Browse Source

- add blender loader

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7554 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
nor..67 14 years ago
parent
commit
36d041fae6
53 changed files with 12931 additions and 120 deletions
  1. 14 5
      engine/nbproject/build-impl.xml
  2. 2 2
      engine/nbproject/genfiles.properties
  3. 114 113
      engine/nbproject/project.properties
  4. 1 0
      engine/nbproject/project.xml
  5. 756 0
      engine/src/blender/com/jme3/asset/BlenderKey.java
  6. 193 0
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
  7. 157 0
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java
  8. 210 0
      engine/src/blender/com/jme3/scene/plugins/blender/data/DnaBlockData.java
  9. 319 0
      engine/src/blender/com/jme3/scene/plugins/blender/data/Field.java
  10. 201 0
      engine/src/blender/com/jme3/scene/plugins/blender/data/FileBlockHeader.java
  11. 311 0
      engine/src/blender/com/jme3/scene/plugins/blender/data/Structure.java
  12. 74 0
      engine/src/blender/com/jme3/scene/plugins/blender/exception/BlenderFileException.java
  13. 132 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/ArmatureHelper.java
  14. 51 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/CameraHelper.java
  15. 37 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/ConstraintHelper.java
  16. 17 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/CurvesHelper.java
  17. 18 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/IpoHelper.java
  18. 48 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/LightHelper.java
  19. 44 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/MaterialHelper.java
  20. 48 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/MeshHelper.java
  21. 48 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/ModifierHelper.java
  22. 51 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/NoiseHelper.java
  23. 48 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/ObjectHelper.java
  24. 17 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/ParticlesHelper.java
  25. 82 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/TextureHelper.java
  26. 370 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ArmatureHelper.java
  27. 57 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CameraHelper.java
  28. 734 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ConstraintHelper.java
  29. 590 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java
  30. 114 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/IpoHelper.java
  31. 99 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/LightHelper.java
  32. 589 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MaterialHelper.java
  33. 599 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java
  34. 527 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ModifierHelper.java
  35. 1531 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/NoiseHelper.java
  36. 407 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java
  37. 118 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ParticlesHelper.java
  38. 1818 0
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/TextureHelper.java
  39. BIN
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/noiseconstants.dat
  40. 225 0
      engine/src/blender/com/jme3/scene/plugins/blender/structures/AbstractInfluenceFunction.java
  41. 137 0
      engine/src/blender/com/jme3/scene/plugins/blender/structures/BezierCurve.java
  42. 162 0
      engine/src/blender/com/jme3/scene/plugins/blender/structures/Constraint.java
  43. 143 0
      engine/src/blender/com/jme3/scene/plugins/blender/structures/ConstraintType.java
  44. 176 0
      engine/src/blender/com/jme3/scene/plugins/blender/structures/Ipo.java
  45. 56 0
      engine/src/blender/com/jme3/scene/plugins/blender/structures/Modifier.java
  46. 102 0
      engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java
  47. 382 0
      engine/src/blender/com/jme3/scene/plugins/blender/utils/BlenderInputStream.java
  48. 400 0
      engine/src/blender/com/jme3/scene/plugins/blender/utils/DataRepository.java
  49. 156 0
      engine/src/blender/com/jme3/scene/plugins/blender/utils/DynamicArray.java
  50. 109 0
      engine/src/blender/com/jme3/scene/plugins/blender/utils/IBlenderConverter.java
  51. 161 0
      engine/src/blender/com/jme3/scene/plugins/blender/utils/JmeConverter.java
  52. 175 0
      engine/src/blender/com/jme3/scene/plugins/blender/utils/Pointer.java
  53. 1 0
      engine/src/desktop/com/jme3/asset/Desktop.cfg

+ 14 - 5
engine/nbproject/build-impl.xml

@@ -180,6 +180,7 @@ is divided into following sections:
                 <available file="${src.lwjgl-oal.dir}"/>
                 <available file="${src.lwjgl-ogl.dir}"/>
                 <available file="${src.ogre.dir}"/>
+                <available file="${src.blender.dir}"/>
                 <available file="${src.pack.dir}"/>
                 <available file="${src.jheora.dir}"/>
                 <available file="${src.test.dir}"/>
@@ -263,6 +264,7 @@ is divided into following sections:
         <fail unless="src.lwjgl-oal.dir">Must set src.lwjgl-oal.dir</fail>
         <fail unless="src.lwjgl-ogl.dir">Must set src.lwjgl-ogl.dir</fail>
         <fail unless="src.ogre.dir">Must set src.ogre.dir</fail>
+        <fail unless="src.blender.dir">Must set src.blender.dir</fail>
         <fail unless="src.pack.dir">Must set src.pack.dir</fail>
         <fail unless="src.jheora.dir">Must set src.jheora.dir</fail>
         <fail unless="src.test.dir">Must set src.test.dir</fail>
@@ -289,7 +291,7 @@ is divided into following sections:
     </target>
     <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-macrodef-javac-with-processors">
         <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}" name="srcdir"/>
+            <attribute default="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.blender.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}" name="srcdir"/>
             <attribute default="${build.classes.dir}" name="destdir"/>
             <attribute default="${javac.classpath}" name="classpath"/>
             <attribute default="${javac.processorpath}" name="processorpath"/>
@@ -329,7 +331,7 @@ is divided into following sections:
     </target>
     <target depends="-init-ap-cmdline-properties" name="-init-macrodef-javac-without-processors" unless="ap.supported.internal">
         <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}" name="srcdir"/>
+            <attribute default="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.blender.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}" name="srcdir"/>
             <attribute default="${build.classes.dir}" name="destdir"/>
             <attribute default="${javac.classpath}" name="classpath"/>
             <attribute default="${javac.processorpath}" name="processorpath"/>
@@ -361,7 +363,7 @@ is divided into following sections:
     </target>
     <target depends="-init-macrodef-javac-with-processors,-init-macrodef-javac-without-processors" name="-init-macrodef-javac">
         <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}" name="srcdir"/>
+            <attribute default="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.blender.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}" name="srcdir"/>
             <attribute default="${build.classes.dir}" name="destdir"/>
             <attribute default="${javac.classpath}" name="classpath"/>
             <sequential>
@@ -659,7 +661,7 @@ is divided into following sections:
                 <include name="*"/>
             </dirset>
         </pathconvert>
-        <j2seproject3:depend srcdir="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}:${build.generated.subdirs}"/>
+        <j2seproject3:depend srcdir="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.blender.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}:${build.generated.subdirs}"/>
     </target>
     <target depends="init,deps-jar,-pre-pre-compile,-pre-compile, -copy-persistence-xml,-compile-depend" if="have.sources" name="-do-compile">
         <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/>
@@ -678,6 +680,7 @@ is divided into following sections:
             <fileset dir="${src.lwjgl-oal.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${src.lwjgl-ogl.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${src.ogre.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+            <fileset dir="${src.blender.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${src.pack.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${src.jheora.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
             <fileset dir="${src.test.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
@@ -703,7 +706,7 @@ is divided into following sections:
     <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single">
         <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
         <j2seproject3:force-recompile/>
-        <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}"/>
+        <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.core.dir}:${src.core-data.dir}:${src.core-plugins.dir}:${src.terrain.dir}:${src.networking.dir}:${src.desktop.dir}:${src.desktop-fx.dir}:${src.games.dir}:${src.jbullet.dir}:${src.niftygui.dir}:${src.jogg.dir}:${src.lwjgl-oal.dir}:${src.lwjgl-ogl.dir}:${src.ogre.dir}:${src.blender.dir}:${src.pack.dir}:${src.jheora.dir}:${src.test.dir}:${src.tools.dir}:${src.xml.dir}"/>
     </target>
     <target name="-post-compile-single">
         <!-- Empty placeholder for easier customization. -->
@@ -965,6 +968,9 @@ is divided into following sections:
             <fileset dir="${src.ogre.dir}" excludes="${excludes}" includes="${includes}">
                 <filename name="**/*.java"/>
             </fileset>
+            <fileset dir="${src.blender.dir}" excludes="${excludes}" includes="${includes}">
+                <filename name="**/*.java"/>
+            </fileset>
             <fileset dir="${src.pack.dir}" excludes="${excludes}" includes="${includes}">
                 <filename name="**/*.java"/>
             </fileset>
@@ -1027,6 +1033,9 @@ is divided into following sections:
             <fileset dir="${src.ogre.dir}" excludes="${excludes}" includes="${includes}">
                 <filename name="**/doc-files/**"/>
             </fileset>
+            <fileset dir="${src.blender.dir}" excludes="${excludes}" includes="${includes}">
+                <filename name="**/doc-files/**"/>
+            </fileset>
             <fileset dir="${src.pack.dir}" excludes="${excludes}" includes="${includes}">
                 <filename name="**/doc-files/**"/>
             </fileset>

+ 2 - 2
engine/nbproject/genfiles.properties

@@ -3,8 +3,8 @@ build.xml.script.CRC32=34d4c2f2
 build.xml.stylesheet.CRC32=958a1d3e
 # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
 # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
-nbproject/build-impl.xml.data.CRC32=759acdca
-nbproject/build-impl.xml.script.CRC32=c2fd0217
+nbproject/build-impl.xml.data.CRC32=8a1eda4b
+nbproject/build-impl.xml.script.CRC32=047b53c9
 nbproject/[email protected]
 nbproject/profiler-build-impl.xml.data.CRC32=aff514c1
 nbproject/profiler-build-impl.xml.script.CRC32=abda56ed

+ 114 - 113
engine/nbproject/project.properties

@@ -1,113 +1,114 @@
-annotation.processing.enabled=false
-annotation.processing.enabled.in.editor=false
-annotation.processing.run.all.processors=true
-ant.customtasks.libs=JWSAntTasks
-application.homepage=http://www.jmonkeyengine.com/
-application.title=jMonkeyEngine 3.0
-application.vendor=jMonkeyEngine
-build.classes.dir=${build.dir}/classes
-build.classes.excludes=**/*.java,**/*.form
-# This directory is removed when the project is cleaned:
-build.dir=build
-build.generated.dir=${build.dir}/generated
-build.generated.sources.dir=${build.dir}/generated-sources
-# Only compile against the classpath explicitly listed here:
-build.sysclasspath=ignore
-build.test.classes.dir=${build.dir}/test/classes
-build.test.results.dir=${build.dir}/test/results
-# Uncomment to specify the preferred debugger connection transport:
-#debug.transport=dt_socket
-debug.classpath=\
-    ${run.classpath}
-debug.test.classpath=\
-    ${run.test.classpath}
-# This directory is removed when the project is cleaned:
-dist.dir=dist
-dist.jar=${dist.dir}/jMonkeyEngine3.jar
-dist.javadoc.dir=${dist.dir}/javadoc
-endorsed.classpath=
-excludes=
-file.reference.src-test-data=src/test-data
-includes=**
-jar.archive.disabled=${jnlp.enabled}
-jar.compress=true
-jar.index=${jnlp.enabled}
-javac.classpath=\
-    ${libs.jogg.classpath}:\
-    ${libs.jbullet.classpath}:\
-    ${libs.bullet.classpath}:\
-    ${libs.lwjgl.classpath}:\
-    ${libs.jheora.classpath}:\
-    ${libs.niftygui1.3.classpath}:\
-    ${libs.jme3-test-data.classpath}:\
-    ${libs.noise.classpath}
-# Space-separated list of extra javac options
-javac.compilerargs=
-javac.deprecation=false
-javac.processorpath=\
-    ${javac.classpath}
-javac.source=1.5
-javac.target=1.5
-javac.test.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}:\
-    ${libs.junit_4.classpath}
-javadoc.additionalparam=
-javadoc.author=false
-javadoc.encoding=${source.encoding}
-javadoc.noindex=false
-javadoc.nonavbar=false
-javadoc.notree=false
-javadoc.private=false
-javadoc.splitindex=true
-javadoc.use=true
-javadoc.version=false
-javadoc.windowtitle=jMonkeyEngine3
-jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api"
-jnlp.applet.class=jme3test.awt.AppHarness
-jnlp.applet.height=300
-jnlp.applet.width=300
-jnlp.codebase.type=user
-jnlp.codebase.user=http://jmonkeyengine.com/javawebstart/
-jnlp.descriptor=application
-jnlp.enabled=false
-jnlp.icon=/Users/normenhansen/Pictures/jme/icons/jme-logo48.png
-jnlp.mixed.code=default
-jnlp.offline-allowed=true
-jnlp.signed=true
-jnlp.signing=generated
-jnlp.signing.alias=
-jnlp.signing.keystore=
-main.class=jme3test.TestChooser
-manifest.file=MANIFEST.MF
-meta.inf.dir=${src.dir}/META-INF
-mkdist.disabled=false
-platform.active=default_platform
-run.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}
-run.jvmargs=-Xms40m -Xmx40m -XX:MaxDirectMemorySize=256M
-run.test.classpath=\
-    ${javac.test.classpath}:\
-    ${build.test.classes.dir}
-source.encoding=UTF-8
-src.core-data.dir=src/core-data
-src.core-plugins.dir=src/core-plugins
-src.core.dir=src/core
-src.desktop-fx.dir=src/desktop-fx
-src.desktop.dir=src/desktop
-src.games.dir=src/games
-src.jbullet.dir=src/jbullet
-src.jheora.dir=src/jheora
-src.jogg.dir=src/jogg
-src.lwjgl-oal.dir=src/lwjgl-oal
-src.lwjgl-ogl.dir=src/lwjgl-ogl
-src.networking.dir=src\\networking
-src.niftygui.dir=src/niftygui
-src.ogre.dir=src/ogre
-src.pack.dir=src/pack
-src.terrain.dir=src/terrain
-src.test.dir=src/test
-src.tools.dir=src/tools
-src.xml.dir=src/xml
-test.test.dir=test
+annotation.processing.enabled=false
+annotation.processing.enabled.in.editor=false
+annotation.processing.run.all.processors=true
+ant.customtasks.libs=JWSAntTasks
+application.homepage=http://www.jmonkeyengine.com/
+application.title=jMonkeyEngine 3.0
+application.vendor=jMonkeyEngine
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+    ${run.classpath}
+debug.test.classpath=\
+    ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/jMonkeyEngine3.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+endorsed.classpath=
+excludes=
+file.reference.src-test-data=src/test-data
+includes=**
+jar.archive.disabled=${jnlp.enabled}
+jar.compress=true
+jar.index=${jnlp.enabled}
+javac.classpath=\
+    ${libs.jogg.classpath}:\
+    ${libs.jbullet.classpath}:\
+    ${libs.bullet.classpath}:\
+    ${libs.lwjgl.classpath}:\
+    ${libs.jheora.classpath}:\
+    ${libs.niftygui1.3.classpath}:\
+    ${libs.jme3-test-data.classpath}:\
+    ${libs.noise.classpath}
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.processorpath=\
+    ${javac.classpath}
+javac.source=1.5
+javac.target=1.5
+javac.test.classpath=\
+    ${javac.classpath}:\
+    ${build.classes.dir}:\
+    ${libs.junit_4.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=jMonkeyEngine3
+jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api"
+jnlp.applet.class=jme3test.awt.AppHarness
+jnlp.applet.height=300
+jnlp.applet.width=300
+jnlp.codebase.type=user
+jnlp.codebase.user=http://jmonkeyengine.com/javawebstart/
+jnlp.descriptor=application
+jnlp.enabled=false
+jnlp.icon=/Users/normenhansen/Pictures/jme/icons/jme-logo48.png
+jnlp.mixed.code=default
+jnlp.offline-allowed=true
+jnlp.signed=true
+jnlp.signing=generated
+jnlp.signing.alias=
+jnlp.signing.keystore=
+main.class=jme3test.TestChooser
+manifest.file=MANIFEST.MF
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=default_platform
+run.classpath=\
+    ${javac.classpath}:\
+    ${build.classes.dir}
+run.jvmargs=-Xms40m -Xmx40m -XX:MaxDirectMemorySize=256M
+run.test.classpath=\
+    ${javac.test.classpath}:\
+    ${build.test.classes.dir}
+source.encoding=UTF-8
+src.blender.dir=src/blender
+src.core-data.dir=src/core-data
+src.core-plugins.dir=src/core-plugins
+src.core.dir=src/core
+src.desktop-fx.dir=src/desktop-fx
+src.desktop.dir=src/desktop
+src.games.dir=src/games
+src.jbullet.dir=src/jbullet
+src.jheora.dir=src/jheora
+src.jogg.dir=src/jogg
+src.lwjgl-oal.dir=src/lwjgl-oal
+src.lwjgl-ogl.dir=src/lwjgl-ogl
+src.networking.dir=src\\networking
+src.niftygui.dir=src/niftygui
+src.ogre.dir=src/ogre
+src.pack.dir=src/pack
+src.terrain.dir=src/terrain
+src.test.dir=src/test
+src.tools.dir=src/tools
+src.xml.dir=src/xml
+test.test.dir=test

+ 1 - 0
engine/nbproject/project.xml

@@ -25,6 +25,7 @@
                 <root id="src.lwjgl-oal.dir" name="LWJGL-OAL"/>
                 <root id="src.lwjgl-ogl.dir" name="LWJGL-OGL"/>
                 <root id="src.ogre.dir" name="Ogre"/>
+                <root id="src.blender.dir" name="Blender"/>
                 <root id="src.pack.dir" name="Pack"/>
                 <root id="src.jheora.dir" name="Jheora"/>
                 <root id="src.test.dir" name="Test"/>

+ 756 - 0
engine/src/blender/com/jme3/asset/BlenderKey.java

@@ -0,0 +1,756 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.asset;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.Set;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.Light;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Node;
+import com.jme3.scene.SceneGraphVisitor;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.ogre.AnimData;
+import com.jme3.texture.Texture;
+
+/**
+ * Blender key. Contains path of the blender file and its loading properties.
+ * @author Marcin Roguski
+ */
+public class BlenderKey extends ModelKey {
+	protected static final int					DEFAULT_FPS				= 25;
+
+	/**
+	 * Animation definitions. The key is the object name that owns the animation. The value is a map between animation
+	 * name and its start and stop frames. Blender stores a pointer for animation within object. Therefore one object
+	 * can only have one animation at the time. We want to be able to switch between animations for one object so we
+	 * need to map the object name to animation names the object will use.
+	 */
+	protected Map<String, Map<String, int[]>>	animations;
+	/**
+	 * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
+	 * between the frames.
+	 */
+	protected int								fps						= DEFAULT_FPS;
+	/** Width of generated textures (in pixels). Blender uses 140x140 by default. */
+	protected int								generatedTextureWidth	= 140;
+	/** Height of generated textures (in pixels). Blender uses 140x140 by default. */
+	protected int								generatedTextureHeight	= 140;
+	/**
+	 * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
+	 */
+	protected int								featuresToLoad			= FeaturesToLoad.ALL;
+	/** The root path for all the assets. */
+	protected String							assetRootPath;
+	/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
+	protected boolean							fixUpAxis				= true;
+	/**
+	 * The name of world settings that the importer will use. If not set or specified name does not occur in the file
+	 * then the first world settings in the file will be used.
+	 */
+	protected String							usedWorld;
+	/**
+	 * User's default material that is set fo objects that have no material definition in blender. The default value is
+	 * null. If the value is null the importer will use its own default material (gray color - like in blender).
+	 */
+	protected Material							defaultMaterial;
+	/** Face cull mode. By default it is disabled. */
+	protected FaceCullMode						faceCullMode			= FaceCullMode.Off;
+
+	/**
+	 * Constructor used by serialization mechanisms.
+	 */
+	public BlenderKey() {}
+
+	/**
+	 * Constructor. Creates a key for the given file name.
+	 * @param name
+	 *        the name (path) of a file
+	 */
+	public BlenderKey(String name) {
+		super(name);
+	}
+
+	/**
+	 * This method adds an animation definition. If a definition already eixists in the key then it is replaced.
+	 * @param objectName
+	 *        the name of animation's owner
+	 * @param name
+	 *        the name of the animation
+	 * @param start
+	 *        the start frame of the animation
+	 * @param stop
+	 *        the stop frame of the animation
+	 */
+	public synchronized void addAnimation(String objectName, String name, int start, int stop) {
+		if(objectName == null) {
+			throw new IllegalArgumentException("Object name cannot be null!");
+		}
+		if(name == null) {
+			throw new IllegalArgumentException("Animation name cannot be null!");
+		}
+		if(start > stop) {
+			throw new IllegalArgumentException("Start frame cannot be greater than stop frame!");
+		}
+		if(animations == null) {
+			animations = new HashMap<String, Map<String, int[]>>();
+			animations.put(objectName, new HashMap<String, int[]>());
+		}
+		Map<String, int[]> objectAnimations = animations.get(objectName);
+		if(objectAnimations == null) {
+			objectAnimations = new HashMap<String, int[]>();
+			animations.put(objectName, objectAnimations);
+		}
+		objectAnimations.put(name, new int[] {start, stop});
+	}
+
+	/**
+	 * This method returns the animation frames boundaries.
+	 * @param objectName
+	 *        the name of animation's owner
+	 * @param name
+	 *        animation name
+	 * @return animation frame boundaries in a table [start, stop] or null if animation of the given name does not
+	 *         exists
+	 */
+	public int[] getAnimationFrames(String objectName, String name) {
+		Map<String, int[]> objectAnimations = animations == null ? null : animations.get(objectName);
+		int[] frames = objectAnimations == null ? null : objectAnimations.get(name);
+		return frames == null ? null : frames.clone();
+	}
+
+	/**
+	 * This method returns the animation names for the given object name.
+	 * @param objectName
+	 *        the name of the object
+	 * @return an array of animations for this object
+	 */
+	public Set<String> getAnimationNames(String objectName) {
+		Map<String, int[]> objectAnimations = animations == null ? null : animations.get(objectName);
+		return objectAnimations == null ? null : objectAnimations.keySet();
+	}
+
+	/**
+	 * This method returns the animations map.
+	 * @return the animations map
+	 */
+	public Map<String, Map<String, int[]>> getAnimations() {
+		return animations;
+	}
+
+	/**
+	 * This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25.
+	 * @return the frames per second amount
+	 */
+	public int getFps() {
+		return fps;
+	}
+
+	/**
+	 * This method sets frames per second amount.
+	 * @param fps
+	 *        the frames per second amount
+	 */
+	public void setFps(int fps) {
+		this.fps = fps;
+	}
+
+	/**
+	 * This method sets the width of generated texture (in pixels). By default the value is 140 px.
+	 * @param generatedTextureWidth
+	 *        the width of generated texture
+	 */
+	public void setGeneratedTextureWidth(int generatedTextureWidth) {
+		this.generatedTextureWidth = generatedTextureWidth;
+	}
+
+	/**
+	 * This method returns the width of generated texture (in pixels). By default the value is 140 px.
+	 * @return the width of generated texture
+	 */
+	public int getGeneratedTextureWidth() {
+		return generatedTextureWidth;
+	}
+
+	/**
+	 * This method sets the height of generated texture (in pixels). By default the value is 140 px.
+	 * @param generatedTextureHeight
+	 *        the height of generated texture
+	 */
+	public void setGeneratedTextureHeight(int generatedTextureHeight) {
+		this.generatedTextureHeight = generatedTextureHeight;
+	}
+
+	/**
+	 * This method returns the height of generated texture (in pixels). By default the value is 140 px.
+	 * @return the height of generated texture
+	 */
+	public int getGeneratedTextureHeight() {
+		return generatedTextureHeight;
+	}
+
+	/**
+	 * This method returns the face cull mode.
+	 * @return the face cull mode
+	 */
+	public FaceCullMode getFaceCullMode() {
+		return faceCullMode;
+	}
+
+	/**
+	 * This method sets the face cull mode.
+	 * @param faceCullMode
+	 *        the face cull mode
+	 */
+	public void setFaceCullMode(FaceCullMode faceCullMode) {
+		this.faceCullMode = faceCullMode;
+	}
+
+	/**
+	 * This method sets the asset root path.
+	 * @param assetRootPath
+	 *        the assets root path
+	 */
+	public void setAssetRootPath(String assetRootPath) {
+		this.assetRootPath = assetRootPath;
+	}
+
+	/**
+	 * This method returns the asset root path.
+	 * @return the asset root path
+	 */
+	public String getAssetRootPath() {
+		return assetRootPath;
+	}
+
+	/**
+	 * This method adds features to be loaded.
+	 * @param featuresToLoad
+	 *        bitwise flag of FeaturesToLoad interface values
+	 */
+	public void includeInLoading(int featuresToLoad) {
+		this.featuresToLoad |= featuresToLoad;
+	}
+
+	/**
+	 * This method removes features from being loaded.
+	 * @param featuresToLoad
+	 *        bitwise flag of FeaturesToLoad interface values
+	 */
+	public void excludeFromLoading(int featuresNotToLoad) {
+		this.featuresToLoad &= ~featuresNotToLoad;
+	}
+
+	/**
+	 * This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by
+	 * the blender file loader.
+	 * @return features that will be loaded by the blender file loader
+	 */
+	public int getFeaturesToLoad() {
+		return featuresToLoad;
+	}
+
+	/**
+	 * This method creates an object where loading results will be stores. Only those features will be allowed to store
+	 * that were specified by features-to-load flag.
+	 * @return an object to store loading results
+	 */
+	public LoadingResults prepareLoadingResults() {
+		return new LoadingResults(featuresToLoad);
+	}
+
+	/**
+	 * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y
+	 * is up axis.
+	 * @param fixUpAxis
+	 *        the up axis state variable
+	 */
+	public void setFixUpAxis(boolean fixUpAxis) {
+		this.fixUpAxis = fixUpAxis;
+	}
+
+	/**
+	 * This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By
+	 * default Y is up axis.
+	 * @return the up axis state variable
+	 */
+	public boolean isFixUpAxis() {
+		return fixUpAxis;
+	}
+
+	/**
+	 * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
+	 * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
+	 * during loading (assumin any exists in the file).
+	 * @param usedWorld
+	 *        the name of the WORLD block used during loading
+	 */
+	public void setUsedWorld(String usedWorld) {
+		this.usedWorld = usedWorld;
+	}
+
+	/**
+	 * This mehtod returns the name of the WORLD data block taht should be used during file loading.
+	 * @return the name of the WORLD block used during loading
+	 */
+	public String getUsedWorld() {
+		return usedWorld;
+	}
+
+	/**
+	 * This method sets the default material for objects.
+	 * @param defaultMaterial
+	 *        the default material
+	 */
+	public void setDefaultMaterial(Material defaultMaterial) {
+		this.defaultMaterial = defaultMaterial;
+	}
+
+	/**
+	 * This method returns the default material.
+	 * @return the default material
+	 */
+	public Material getDefaultMaterial() {
+		return defaultMaterial;
+	}
+
+	@Override
+	public void write(JmeExporter e) throws IOException {
+		super.write(e);
+		OutputCapsule oc = e.getCapsule(this);
+		//saving animations
+		oc.write(animations == null ? 0 : animations.size(), "anim-size", 0);
+		if(animations != null) {
+			int objectCounter = 0;
+			for(Entry<String, Map<String, int[]>> animEntry : animations.entrySet()) {
+				oc.write(animEntry.getKey(), "animated-object-" + objectCounter, null);
+				int animsAmount = animEntry.getValue().size();
+				oc.write(animsAmount, "anims-amount-" + objectCounter, 0);
+				for(Entry<String, int[]> animsEntry : animEntry.getValue().entrySet()) {
+					oc.write(animsEntry.getKey(), "anim-name-" + objectCounter, null);
+					oc.write(animsEntry.getValue(), "anim-frames-" + objectCounter, null);
+				}
+				++objectCounter;
+			}
+		}
+		//saving the rest of the data
+		oc.write(fps, "fps", DEFAULT_FPS);
+		oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL);
+		oc.write(assetRootPath, "asset-root-path", null);
+		oc.write(fixUpAxis, "fix-up-axis", true);
+		oc.write(usedWorld, "used-world", null);
+		oc.write(defaultMaterial, "default-material", null);
+		oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off);
+	}
+
+	@Override
+	public void read(JmeImporter e) throws IOException {
+		super.read(e);
+		InputCapsule ic = e.getCapsule(this);
+		//reading animations
+		int animSize = ic.readInt("anim-size", 0);
+		if(animSize > 0) {
+			if(animations == null) {
+				animations = new HashMap<String, Map<String, int[]>>(animSize);
+			} else {
+				animations.clear();
+			}
+			for(int i = 0; i < animSize; ++i) {
+				String objectName = ic.readString("animated-object-" + i, null);
+				int animationsAmount = ic.readInt("anims-amount-" + i, 0);
+				Map<String, int[]> objectAnimations = new HashMap<String, int[]>(animationsAmount);
+				for(int j = 0; j < animationsAmount; ++j) {
+					String animName = ic.readString("anim-name-" + i, null);
+					int[] animFrames = ic.readIntArray("anim-frames-" + i, null);
+					objectAnimations.put(animName, animFrames);
+				}
+				animations.put(objectName, objectAnimations);
+			}
+		}
+
+		//reading the rest of the data
+		fps = ic.readInt("fps", DEFAULT_FPS);
+		featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL);
+		assetRootPath = ic.readString("asset-root-path", null);
+		fixUpAxis = ic.readBoolean("fix-up-axis", true);
+		usedWorld = ic.readString("used-world", null);
+		defaultMaterial = (Material)ic.readSavable("default-material", null);
+		faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off);
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = super.hashCode();
+		result = prime * result + (animations == null ? 0 : animations.hashCode());
+		result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());
+		result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());
+		result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());
+		result = prime * result + featuresToLoad;
+		result = prime * result + (fixUpAxis ? 1231 : 1237);
+		result = prime * result + fps;
+		result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(!super.equals(obj)) {
+			return false;
+		}
+		if(this.getClass() != obj.getClass()) {
+			return false;
+		}
+		BlenderKey other = (BlenderKey)obj;
+		if(animations == null) {
+			if(other.animations != null) {
+				return false;
+			}
+		} else if(!animations.equals(other.animations)) {
+			return false;
+		}
+		if(assetRootPath == null) {
+			if(other.assetRootPath != null) {
+				return false;
+			}
+		} else if(!assetRootPath.equals(other.assetRootPath)) {
+			return false;
+		}
+		if(defaultMaterial == null) {
+			if(other.defaultMaterial != null) {
+				return false;
+			}
+		} else if(!defaultMaterial.equals(other.defaultMaterial)) {
+			return false;
+		}
+		if(faceCullMode != other.faceCullMode) {
+			return false;
+		}
+		if(featuresToLoad != other.featuresToLoad) {
+			return false;
+		}
+		if(fixUpAxis != other.fixUpAxis) {
+			return false;
+		}
+		if(fps != other.fps) {
+			return false;
+		}
+		if(usedWorld == null) {
+			if(other.usedWorld != null) {
+				return false;
+			}
+		} else if(!usedWorld.equals(other.usedWorld)) {
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * This interface describes the features of the scene that are to be loaded.
+	 * @author Marcin Roguski
+	 */
+	public static interface FeaturesToLoad {
+		int	SCENES		= 0x0000FFFF;
+		int	OBJECTS		= 0x0000000B;
+		int	ANIMATIONS	= 0x00000004;
+		int	MATERIALS	= 0x00000003;
+		int	TEXTURES	= 0x00000001;
+		int	CAMERAS		= 0x00000020;
+		int	LIGHTS		= 0x00000010;
+		int	ALL			= 0xFFFFFFFF;
+	}
+
+	/**
+	 * This class holds the loading results according to the given loading flag.
+	 * @author Marcin Roguski
+	 */
+	public static class LoadingResults extends Spatial {
+		/** Bitwise mask of features that are to be loaded. */
+		private final int		featuresToLoad;
+		/** The scenes from the file. */
+		private List<Node>		scenes;
+		/** Objects from all scenes. */
+		private List<Node>		objects;
+		/** Materials from all objects. */
+		private List<Material>	materials;
+		/** Textures from all objects. */
+		private List<Texture>	textures;
+		/** Animations of all objects. */
+		private List<AnimData>	animations;
+		/** All cameras from the file. */
+		private List<Camera>	cameras;
+		/** All lights from the file. */
+		private List<Light>		lights;
+
+		/**
+		 * Private constructor prevents users to create an instance of this class from outside the
+		 * @param featuresToLoad
+		 *        bitwise mask of features that are to be loaded
+		 * @see FeaturesToLoad FeaturesToLoad
+		 */
+		private LoadingResults(int featuresToLoad) {
+			this.featuresToLoad = featuresToLoad;
+			if((featuresToLoad & FeaturesToLoad.SCENES) != 0) {
+				scenes = new ArrayList<Node>();
+			}
+			if((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) {
+				objects = new ArrayList<Node>();
+				if((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) {
+					materials = new ArrayList<Material>();
+					if((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) {
+						textures = new ArrayList<Texture>();
+					}
+				}
+				if((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) {
+					animations = new ArrayList<AnimData>();
+				}
+			}
+			if((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
+				cameras = new ArrayList<Camera>();
+			}
+			if((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) {
+				lights = new ArrayList<Light>();
+			}
+		}
+
+		/**
+		 * This method returns a bitwise flag describing what features of the blend file will be included in the result.
+		 * @return bitwise mask of features that are to be loaded
+		 * @see FeaturesToLoad FeaturesToLoad
+		 */
+		public int getLoadedFeatures() {
+			return featuresToLoad;
+		}
+
+		/**
+		 * This method adds a scene to the result set.
+		 * @param scene
+		 *        scene to be added to the result set
+		 */
+		public void addScene(Node scene) {
+			if(scenes != null) {
+				scenes.add(scene);
+			}
+		}
+
+		/**
+		 * This method adds an object to the result set.
+		 * @param object
+		 *        object to be added to the result set
+		 */
+		public void addObject(Node object) {
+			if(objects != null) {
+				objects.add(object);
+			}
+		}
+
+		/**
+		 * This method adds a material to the result set.
+		 * @param material
+		 *        material to be added to the result set
+		 */
+		public void addMaterial(Material material) {
+			if(materials != null) {
+				materials.add(material);
+			}
+		}
+
+		/**
+		 * This method adds a texture to the result set.
+		 * @param texture
+		 *        texture to be added to the result set
+		 */
+		public void addTexture(Texture texture) {
+			if(textures != null) {
+				textures.add(texture);
+			}
+		}
+
+		/**
+		 * This method adds a camera to the result set.
+		 * @param camera
+		 *        camera to be added to the result set
+		 */
+		public void addCamera(Camera camera) {
+			if(cameras != null) {
+				cameras.add(camera);
+			}
+		}
+
+		/**
+		 * This method adds a light to the result set.
+		 * @param light
+		 *        light to be added to the result set
+		 */
+		@Override
+		public void addLight(Light light) {
+			if(lights != null) {
+				lights.add(light);
+			}
+		}
+
+		/**
+		 * This method returns all loaded scenes.
+		 * @return all loaded scenes
+		 */
+		public List<Node> getScenes() {
+			return scenes;
+		}
+
+		/**
+		 * This method returns all loaded objects.
+		 * @return all loaded objects
+		 */
+		public List<Node> getObjects() {
+			return objects;
+		}
+
+		/**
+		 * This method returns all loaded materials.
+		 * @return all loaded materials
+		 */
+		public List<Material> getMaterials() {
+			return materials;
+		}
+
+		/**
+		 * This method returns all loaded textures.
+		 * @return all loaded textures
+		 */
+		public List<Texture> getTextures() {
+			return textures;
+		}
+
+		/**
+		 * This method returns all loaded animations.
+		 * @return all loaded animations
+		 */
+		public List<AnimData> getAnimations() {
+			return animations;
+		}
+
+		/**
+		 * This method returns all loaded cameras.
+		 * @return all loaded cameras
+		 */
+		public List<Camera> getCameras() {
+			return cameras;
+		}
+
+		/**
+		 * This method returns all loaded lights.
+		 * @return all loaded lights
+		 */
+		public List<Light> getLights() {
+			return lights;
+		}
+
+		@Override
+		public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
+			return 0;
+		}
+
+		@Override
+		public void updateModelBound() {}
+
+		@Override
+		public void setModelBound(BoundingVolume modelBound) {}
+
+		@Override
+		public int getVertexCount() {
+			return 0;
+		}
+
+		@Override
+		public int getTriangleCount() {
+			return 0;
+		}
+
+		@Override
+		public Spatial deepClone() {
+			return null;
+		}
+
+		@Override
+		public void depthFirstTraversal(SceneGraphVisitor visitor) {}
+
+		@Override
+		protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {}
+	}
+
+	/**
+	 * The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient
+	 * light.
+	 * @author Marcin Roguski
+	 */
+	public static class WorldData {
+		/** The ambient light. */
+		private AmbientLight	ambientLight;
+
+		/**
+		 * This method returns the world's ambient light.
+		 * @return the world's ambient light
+		 */
+		public AmbientLight getAmbientLight() {
+			return ambientLight;
+		}
+
+		/**
+		 * This method sets the world's ambient light.
+		 * @param ambientLight
+		 *        the world's ambient light
+		 */
+		public void setAmbientLight(AmbientLight ambientLight) {
+			this.ambientLight = ambientLight;
+		}
+	}
+}

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

@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.BlenderKey;
+import com.jme3.asset.BlenderKey.FeaturesToLoad;
+import com.jme3.asset.BlenderKey.LoadingResults;
+import com.jme3.asset.BlenderKey.WorldData;
+import com.jme3.asset.ModelKey;
+import com.jme3.light.Light;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Node;
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.helpers.ArmatureHelper;
+import com.jme3.scene.plugins.blender.helpers.CameraHelper;
+import com.jme3.scene.plugins.blender.helpers.ConstraintHelper;
+import com.jme3.scene.plugins.blender.helpers.CurvesHelper;
+import com.jme3.scene.plugins.blender.helpers.IpoHelper;
+import com.jme3.scene.plugins.blender.helpers.LightHelper;
+import com.jme3.scene.plugins.blender.helpers.MaterialHelper;
+import com.jme3.scene.plugins.blender.helpers.MeshHelper;
+import com.jme3.scene.plugins.blender.helpers.ModifierHelper;
+import com.jme3.scene.plugins.blender.helpers.NoiseHelper;
+import com.jme3.scene.plugins.blender.helpers.ObjectHelper;
+import com.jme3.scene.plugins.blender.helpers.ParticlesHelper;
+import com.jme3.scene.plugins.blender.helpers.TextureHelper;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.JmeConverter;
+
+/**
+ * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
+ * @author Marcin Roguski
+ */
+public class BlenderLoader implements AssetLoader {
+	private static final Logger	LOGGER	= Logger.getLogger(BlenderLoader.class.getName());
+
+	@Override
+	public LoadingResults load(AssetInfo assetInfo) throws IOException {
+		try {
+			//registering loaders
+			ModelKey modelKey = (ModelKey)assetInfo.getKey();
+			BlenderKey blenderKey;
+			if(modelKey instanceof BlenderKey) {
+				blenderKey = (BlenderKey)modelKey;
+			} else {
+				blenderKey = new BlenderKey(modelKey.getName());
+				blenderKey.setAssetRootPath(modelKey.getFolder());
+			}
+
+			//opening stream
+			BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream(), assetInfo.getManager());
+
+			//reading blocks
+			List<FileBlockHeader> blocks = new ArrayList<FileBlockHeader>();
+			FileBlockHeader fileBlock;
+			DataRepository dataRepository = new DataRepository();
+			dataRepository.setAssetManager(assetInfo.getManager());
+			dataRepository.setInputStream(inputStream);
+			dataRepository.setBlenderKey(blenderKey);
+			
+			//creating helpers
+			dataRepository.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), dataRepository));
+			dataRepository.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(NoiseHelper.class, new NoiseHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber()));
+			
+			//setting additional data to helpers
+			if(blenderKey.isFixUpAxis()) {
+				ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+				objectHelper.setyIsUpAxis(true);
+				CurvesHelper curvesHelper = dataRepository.getHelper(CurvesHelper.class);
+				curvesHelper.setyIsUpAxis(true);
+			}
+			MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+			materialHelper.setFaceCullMode(blenderKey.getFaceCullMode());
+
+			//reading the blocks (dna block is automatically saved in the data repository when found)//TODO: zmienić to
+			do {
+				fileBlock = new FileBlockHeader(inputStream, dataRepository);
+				if(!fileBlock.isDnaBlock()) {
+					blocks.add(fileBlock);
+				}
+			} while(!fileBlock.isLastBlock());
+
+			JmeConverter converter = new JmeConverter(dataRepository);
+			LoadingResults loadingResults = blenderKey.prepareLoadingResults();
+			WorldData worldData = null;//a set of data used in different scene aspects
+			for(FileBlockHeader block : blocks) {
+				switch(block.getCode()) {
+					case FileBlockHeader.BLOCK_OB00://Object
+						Object object = converter.toObject(block.getStructure(dataRepository));
+						if(object instanceof Node) {
+							if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) {
+								LOGGER.log(Level.INFO, ((Node)object).getName() + ": " + ((Node)object).getLocalTranslation().toString() + "--> " + (((Node)object).getParent() == null ? "null" : ((Node)object).getParent().getName()));
+								if(((Node)object).getParent() == null) {
+									loadingResults.addObject((Node)object);
+								}
+							}
+						} else if(object instanceof Camera) {
+							if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0) {
+								loadingResults.addCamera((Camera)object);
+							}
+						} else if(object instanceof Light) {
+							if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
+								loadingResults.addLight((Light)object);
+							}
+						}
+						break;
+					case FileBlockHeader.BLOCK_MA00://Material
+						if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
+							loadingResults.addMaterial(converter.toMaterial(block.getStructure(dataRepository)));
+						}
+						break;
+					case FileBlockHeader.BLOCK_SC00://Scene
+						if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) {
+							loadingResults.addScene(converter.toScene(block.getStructure(dataRepository)));
+						}
+						break;
+					case FileBlockHeader.BLOCK_WO00://World
+						if(worldData == null) {//onlu one world data is used
+							Structure worldStructure = block.getStructure(dataRepository);
+							String worldName = worldStructure.getName();
+							if(blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
+								worldData = converter.toWorldData(worldStructure);
+								if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
+									loadingResults.addLight(worldData.getAmbientLight());
+								}
+							}
+						}
+						break;
+				}
+			}
+			try {
+				inputStream.close();
+			} catch(IOException e) {
+				LOGGER.log(Level.SEVERE, e.getMessage(), e);
+			}
+			return loadingResults;
+		} catch(BlenderFileException e) {
+			LOGGER.log(Level.SEVERE, e.getMessage(), e);
+		}
+		return null;
+	}
+}

+ 157 - 0
engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java

@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.BlenderKey;
+import com.jme3.asset.BlenderKey.LoadingResults;
+import com.jme3.asset.ModelKey;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.helpers.ArmatureHelper;
+import com.jme3.scene.plugins.blender.helpers.CameraHelper;
+import com.jme3.scene.plugins.blender.helpers.ConstraintHelper;
+import com.jme3.scene.plugins.blender.helpers.CurvesHelper;
+import com.jme3.scene.plugins.blender.helpers.IpoHelper;
+import com.jme3.scene.plugins.blender.helpers.LightHelper;
+import com.jme3.scene.plugins.blender.helpers.MaterialHelper;
+import com.jme3.scene.plugins.blender.helpers.MeshHelper;
+import com.jme3.scene.plugins.blender.helpers.ModifierHelper;
+import com.jme3.scene.plugins.blender.helpers.NoiseHelper;
+import com.jme3.scene.plugins.blender.helpers.ObjectHelper;
+import com.jme3.scene.plugins.blender.helpers.ParticlesHelper;
+import com.jme3.scene.plugins.blender.helpers.TextureHelper;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.JmeConverter;
+
+/**
+ * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
+ * @author Marcin Roguski
+ */
+public class BlenderModelLoader implements AssetLoader {
+	private static final Logger	LOGGER	= Logger.getLogger(BlenderModelLoader.class.getName());
+
+	@Override
+	public Spatial load(AssetInfo assetInfo) throws IOException {
+		try {
+			//registering loaders
+			ModelKey modelKey = (ModelKey)assetInfo.getKey();
+			BlenderKey blenderKey;
+			if(modelKey instanceof BlenderKey) {
+				blenderKey = (BlenderKey)modelKey;
+			} else {
+				blenderKey = new BlenderKey(modelKey.getName());
+				blenderKey.setAssetRootPath(modelKey.getFolder());
+			}
+
+			//opening stream
+			BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream(), assetInfo.getManager());
+			List<FileBlockHeader> blocks = new ArrayList<FileBlockHeader>();
+			FileBlockHeader fileBlock;
+			DataRepository dataRepository = new DataRepository();
+			dataRepository.setAssetManager(assetInfo.getManager());
+			dataRepository.setInputStream(inputStream);
+			dataRepository.setBlenderKey(blenderKey);
+			dataRepository.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), dataRepository));
+			dataRepository.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(NoiseHelper.class, new NoiseHelper(inputStream.getVersionNumber()));
+			dataRepository.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber()));
+			
+			//setting additional data to helpers
+			if(blenderKey.isFixUpAxis()) {
+				ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+				objectHelper.setyIsUpAxis(true);
+				CurvesHelper curvesHelper = dataRepository.getHelper(CurvesHelper.class);
+				curvesHelper.setyIsUpAxis(true);
+			}
+			MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+			materialHelper.setFaceCullMode(blenderKey.getFaceCullMode());
+
+			//reading the blocks (dna block is automatically saved in the data repository when found)//TODO: zmienić to
+			do {
+				fileBlock = new FileBlockHeader(inputStream, dataRepository);
+				if(!fileBlock.isDnaBlock()) {
+					blocks.add(fileBlock);
+				}
+			} while(!fileBlock.isLastBlock());
+
+			JmeConverter converter = new JmeConverter(dataRepository);
+			LoadingResults loadingResults = blenderKey.prepareLoadingResults();
+			for(FileBlockHeader block : blocks) {
+				if(block.getCode() == FileBlockHeader.BLOCK_OB00) {
+					Object object = converter.toObject(block.getStructure(dataRepository));
+					if(object instanceof Node) {
+						LOGGER.log(Level.INFO, ((Node)object).getName() + ": " + ((Node)object).getLocalTranslation().toString() + "--> " + (((Node)object).getParent() == null ? "null" : ((Node)object).getParent().getName()));
+						if(((Node)object).getParent() == null) {
+							loadingResults.addObject((Node)object);
+						}
+					}
+				}
+			}
+			inputStream.close();
+			List<Node> objects = loadingResults.getObjects();
+			if(objects.size() > 0) {
+				Node modelNode = new Node(blenderKey.getName());
+				for(Iterator<Node> it = objects.iterator(); it.hasNext();) {
+					Node node = it.next();
+					modelNode.attachChild(node);
+				}
+				return modelNode;
+			} else if(objects.size() == 1) {
+				return objects.get(0);
+			}
+		} catch(BlenderFileException e) {
+			LOGGER.log(Level.SEVERE, e.getMessage(), e);
+		}
+		return null;
+	}
+}

+ 210 - 0
engine/src/blender/com/jme3/scene/plugins/blender/data/DnaBlockData.java

@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+
+/**
+ * The data block containing the description of the file.
+ * @author Marcin Roguski
+ */
+public class DnaBlockData {
+	private static final int				SDNA_ID	= 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A';	//SDNA
+	private static final int				NAME_ID	= 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E';	//NAME
+	private static final int				TYPE_ID	= 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E';	//TYPE
+	private static final int				TLEN_ID	= 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N';	//TLEN
+	private static final int				STRC_ID	= 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C';	//STRC
+
+	/** Structures available inside the file. */
+	private final Structure[]				structures;
+	/** A map that helps finding a structure by type. */
+	private final Map<String, Structure>	structuresMap;
+
+	/**
+	 * Constructor. Loads the block from the given stream during instance creation.
+	 * @param inputStream
+	 *        the stream we read the block from
+	 * @param dataRepository
+	 *        the data repository
+	 * @throws BlenderFileException
+	 *         this exception is throw if the blend file is invalid or somehow corrupted
+	 */
+	public DnaBlockData(BlenderInputStream inputStream, DataRepository dataRepository) throws BlenderFileException {
+		int identifier;
+
+		//reading 'SDNA' identifier
+		identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 |
+					 inputStream.readByte() << 8 | inputStream.readByte();
+
+		if(identifier != SDNA_ID) {
+			throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier));
+		}
+
+		//reading names
+		identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 |
+					 inputStream.readByte() << 8 | inputStream.readByte();
+		if(identifier != NAME_ID) {
+			throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier));
+		}
+		int amount = inputStream.readInt();
+		if(amount <= 0) {
+			throw new BlenderFileException("The names amount number should be positive!");
+		}
+		String[] names = new String[amount];
+		for(int i = 0; i < amount; ++i) {
+			names[i] = inputStream.readString();
+		}
+
+		//reding types
+		inputStream.alignPosition(4);
+		identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 |
+					 inputStream.readByte() << 8 | inputStream.readByte();
+		if(identifier != TYPE_ID) {
+			throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier));
+		}
+		amount = inputStream.readInt();
+		if(amount <= 0) {
+			throw new BlenderFileException("The types amount number should be positive!");
+		}
+		String[] types = new String[amount];
+		for(int i = 0; i < amount; ++i) {
+			types[i] = inputStream.readString();
+		}
+
+		//reading lengths
+		inputStream.alignPosition(4);
+		identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 |
+					 inputStream.readByte() << 8 | inputStream.readByte();
+		if(identifier != TLEN_ID) {
+			throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier));
+		}
+		int[] lengths = new int[amount];//theamount is the same as int types
+		for(int i = 0; i < amount; ++i) {
+			lengths[i] = inputStream.readShort();
+		}
+
+		//reading structures
+		inputStream.alignPosition(4);
+		identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 |
+					 inputStream.readByte() << 8 | inputStream.readByte();
+		if(identifier != STRC_ID) {
+			throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier));
+		}
+		amount = inputStream.readInt();
+		if(amount <= 0) {
+			throw new BlenderFileException("The structures amount number should be positive!");
+		}
+		structures = new Structure[amount];
+		structuresMap = new HashMap<String, Structure>(amount);
+		for(int i = 0; i < amount; ++i) {
+			structures[i] = new Structure(inputStream, names, types, dataRepository);
+			if(structuresMap.containsKey(structures[i].getType())) {
+				throw new BlenderFileException("Blend file seems to be corrupted! The type " + structures[i].getType() + " is defined twice!");
+			}
+			structuresMap.put(structures[i].getType(), structures[i]);
+		}
+	}
+
+	/**
+	 * This method returns the amount of the structures.
+	 * @return the amount of the structures
+	 */
+	public int getStructuresCount() {
+		return structures.length;
+	}
+
+	/**
+	 * This method returns the structure of the given index.
+	 * @param index
+	 *        the index of the structure
+	 * @return the structure of the given index
+	 */
+	public Structure getStructure(int index) {
+		try {
+			return (Structure)structures[index].clone();
+		} catch(CloneNotSupportedException e) {
+			throw new IllegalStateException("Structure should be clonable!!!", e);
+		}
+	}
+
+	/**
+	 * This method returns a structure of the given name. If the name does not exists then null is returned.
+	 * @param name
+	 *        the name of the structure
+	 * @return the required structure or null if the given name is inapropriate
+	 */
+	public Structure getStructure(String name) {
+		try {
+			return (Structure)structuresMap.get(name).clone();
+		} catch(CloneNotSupportedException e) {
+			throw new IllegalStateException(e.getMessage(), e);
+		}
+	}
+
+	/**
+	 * This method indicates if the structure of the given name exists.
+	 * @param name
+	 *        the name of the structure
+	 * @return true if the structure exists and false otherwise
+	 */
+	public boolean hasStructure(String name) {
+		return structuresMap.containsKey(name);
+	}
+
+	/**
+	 * This method converts the given identifier code to string.
+	 * @param code
+	 *        the code taht is to be converted
+	 * @return the string value of the identifier
+	 */
+	private String toString(int code) {
+		char c1 = (char)((code & 0xFF000000) >> 24);
+		char c2 = (char)((code & 0xFF0000) >> 16);
+		char c3 = (char)((code & 0xFF00) >> 8);
+		char c4 = (char)(code & 0xFF);
+		return String.valueOf(c1) + c2 + c3 + c4;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder stringBuilder = new StringBuilder("=============== ").append(SDNA_ID).append('\n');
+		for(Structure structure : structures) {
+			stringBuilder.append(structure.toString()).append('\n');
+		}
+		return stringBuilder.append("===============").toString();
+	}
+}

+ 319 - 0
engine/src/blender/com/jme3/scene/plugins/blender/data/Field.java

@@ -0,0 +1,319 @@
+package com.jme3.scene.plugins.blender.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.jme3.scene.plugins.blender.data.Structure.DataType;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+/**
+ * This class represents a single field in the structure. It can be either a primitive type or a table or a reference to
+ * another structure.
+ * @author Marcin Roguski
+ */
+/*package*/class Field implements Cloneable {
+	private static final int	NAME_LENGTH	= 24;
+	private static final int	TYPE_LENGTH	= 16;
+
+	/** The data repository. */
+	public DataRepository		dataRepository;
+	/** The type of the field. */
+	public String				type;
+	/** The name of the field. */
+	public String				name;
+	/** The value of the field. Filled during data reading. */
+	public Object				value;
+	/** This variable indicates the level of the pointer. */
+	public int					pointerLevel;
+	/**
+	 * This variable determines the sizes of the array. If the value is null the n the field is not an array.
+	 */
+	public int[]				tableSizes;
+	/** This variable indicates if the field is a function pointer. */
+	public boolean				function;
+
+	/**
+	 * Constructor. Saves the field data and parses its name.
+	 * @param name
+	 *        the name of the field
+	 * @param type
+	 *        the type of the field
+	 * @param dataRepository
+	 *        the data repository
+	 * @throws BlenderFileException
+	 *         this exception is thrown if the names contain errors
+	 */
+	public Field(String name, String type, DataRepository dataRepository) throws BlenderFileException {
+		this.type = type;
+		this.dataRepository = dataRepository;
+		this.parseField(new StringBuilder(name));
+	}
+
+	/**
+	 * Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we
+	 * have a clead empty copy of the filed to fill with data.
+	 * @param field
+	 *        the object that we copy
+	 */
+	private Field(Field field) {
+		type = field.type;
+		name = field.name;
+		dataRepository = field.dataRepository;
+		pointerLevel = field.pointerLevel;
+		if(field.tableSizes != null) {
+			tableSizes = field.tableSizes.clone();
+		}
+		function = field.function;
+	}
+
+	@Override
+	public Object clone() throws CloneNotSupportedException {
+		return new Field(this);
+	}
+
+	/**
+	 * This method fills the field wth data read from the input stream.
+	 * @param blenderInputStream
+	 *        the stream we read data from
+	 * @throws BlenderFileException
+	 *         an exception is thrown when the blend file is somehow invalid or corrupted
+	 */
+	public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException {
+		int dataToRead = 1;
+		if(tableSizes != null && tableSizes.length > 0) {
+			for(int size : tableSizes) {
+				if(size <= 0) {
+					throw new BlenderFileException("The field " + name + " has invalid table size: " + size);
+				}
+				dataToRead *= size;
+			}
+		}
+		DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, dataRepository) : DataType.POINTER;
+		switch(dataType) {
+			case POINTER:
+				if(dataToRead == 1) {
+					Pointer pointer = new Pointer(pointerLevel, function, dataRepository);
+					pointer.fill(blenderInputStream);
+					value = pointer;
+				} else {
+					Pointer[] data = new Pointer[dataToRead];
+					for(int i = 0; i < dataToRead; ++i) {
+						Pointer pointer = new Pointer(pointerLevel, function, dataRepository);
+						pointer.fill(blenderInputStream);
+						data[i] = pointer;
+					}
+					value = new DynamicArray<Pointer>(tableSizes, data);
+				}
+				break;
+			case CHARACTER:
+				//character is also stored as a number, because sometimes the new blender version uses
+				//other number type instead of character as a field type
+				//and characters are very often used as byte number stores instead of real chars
+				if(dataToRead == 1) {
+					value = Byte.valueOf((byte)blenderInputStream.readByte());
+				} else {
+					Character[] data = new Character[dataToRead];
+					for(int i = 0; i < dataToRead; ++i) {
+						data[i] = Character.valueOf((char)blenderInputStream.readByte());
+					}
+					value = new DynamicArray<Character>(tableSizes, data);
+				}
+				break;
+			case SHORT:
+				if(dataToRead == 1) {
+					value = Integer.valueOf(blenderInputStream.readShort());
+				} else {
+					Number[] data = new Number[dataToRead];
+					for(int i = 0; i < dataToRead; ++i) {
+						data[i] = Integer.valueOf(blenderInputStream.readShort());
+					}
+					value = new DynamicArray<Number>(tableSizes, data);
+				}
+				break;
+			case INTEGER:
+				if(dataToRead == 1) {
+					value = Integer.valueOf(blenderInputStream.readInt());
+				} else {
+					Number[] data = new Number[dataToRead];
+					for(int i = 0; i < dataToRead; ++i) {
+						data[i] = Integer.valueOf(blenderInputStream.readInt());
+					}
+					value = new DynamicArray<Number>(tableSizes, data);
+				}
+				break;
+			case LONG:
+				if(dataToRead == 1) {
+					value = Long.valueOf(blenderInputStream.readLong());
+				} else {
+					Number[] data = new Number[dataToRead];
+					for(int i = 0; i < dataToRead; ++i) {
+						data[i] = Long.valueOf(blenderInputStream.readLong());
+					}
+					value = new DynamicArray<Number>(tableSizes, data);
+				}
+				break;
+			case FLOAT:
+				if(dataToRead == 1) {
+					value = Float.valueOf(blenderInputStream.readFloat());
+				} else {
+					Number[] data = new Number[dataToRead];
+					for(int i = 0; i < dataToRead; ++i) {
+						data[i] = Float.valueOf(blenderInputStream.readFloat());
+					}
+					value = new DynamicArray<Number>(tableSizes, data);
+				}
+				break;
+			case DOUBLE:
+				if(dataToRead == 1) {
+					value = Double.valueOf(blenderInputStream.readDouble());
+				} else {
+					Number[] data = new Number[dataToRead];
+					for(int i = 0; i < dataToRead; ++i) {
+						data[i] = Double.valueOf(blenderInputStream.readDouble());
+					}
+					value = new DynamicArray<Number>(tableSizes, data);
+				}
+				break;
+			case VOID:
+				break;
+			case STRUCTURE:
+				if(dataToRead == 1) {
+					Structure structure = dataRepository.getDnaBlockData().getStructure(type);
+					structure.fill(blenderInputStream);
+					value = structure;
+				} else {
+					Structure[] data = new Structure[dataToRead];
+					for(int i = 0; i < dataToRead; ++i) {
+						Structure structure = dataRepository.getDnaBlockData().getStructure(type);
+						structure.fill(blenderInputStream);
+						data[i] = structure;
+					}
+					value = new DynamicArray<Structure>(tableSizes, data);
+				}
+				break;
+			default:
+				throw new IllegalStateException("Unimplemented filling of type: " + type);
+		}
+	}
+
+	/**
+	 * This method parses the field name to determine how the field should be used.
+	 * @param nameBuilder
+	 *        the name of the field (given as StringBuilder)
+	 * @throws BlenderFileException
+	 *         this exception is thrown if the names contain errors
+	 */
+	private void parseField(StringBuilder nameBuilder) throws BlenderFileException {
+		this.removeWhitespaces(nameBuilder);
+		//veryfying if the name is a pointer
+		int pointerIndex = nameBuilder.indexOf("*");
+		while(pointerIndex >= 0) {
+			++pointerLevel;
+			nameBuilder.deleteCharAt(pointerIndex);
+			pointerIndex = nameBuilder.indexOf("*");
+		}
+		//veryfying if the name is a function pointer
+		if(nameBuilder.indexOf("(") >= 0) {
+			function = true;
+			this.removeCharacter(nameBuilder, '(');
+			this.removeCharacter(nameBuilder, ')');
+		} else {
+			//veryfying if the name is a table
+			int tableStartIndex = 0;
+			List<Integer> lengths = new ArrayList<Integer>(3);//3 dimensions will be enough in most cases
+			do {
+				tableStartIndex = nameBuilder.indexOf("[");
+				if(tableStartIndex > 0) {
+					int tableStopIndex = nameBuilder.indexOf("]");
+					if(tableStopIndex < 0) {
+						throw new BlenderFileException("Invalid structure name: " + name);
+					}
+					try {
+						lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex)));
+					} catch(NumberFormatException e) {
+						throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e);
+					}
+					nameBuilder.delete(tableStartIndex, tableStopIndex + 1);
+				}
+			} while(tableStartIndex > 0);
+			if(!lengths.isEmpty()) {
+				tableSizes = new int[lengths.size()];
+				for(int i = 0; i < tableSizes.length; ++i) {
+					tableSizes[i] = lengths.get(i).intValue();
+				}
+			}
+		}
+		name = nameBuilder.toString();
+	}
+
+	/**
+	 * This method removes the required character from the text.
+	 * @param text
+	 *        the text we remove characters from
+	 * @param toRemove
+	 *        the character to be removed
+	 */
+	private void removeCharacter(StringBuilder text, char toRemove) {
+		for(int i = 0; i < text.length(); ++i) {
+			if(text.charAt(i) == toRemove) {
+				text.deleteCharAt(i);
+				--i;
+			}
+		}
+	}
+
+	/**
+	 * This method removes all whitespaces from the text.
+	 * @param text
+	 *        the text we remove whitespaces from
+	 */
+	private void removeWhitespaces(StringBuilder text) {
+		for(int i = 0; i < text.length(); ++i) {
+			if(Character.isWhitespace(text.charAt(i))) {
+				text.deleteCharAt(i);
+				--i;
+			}
+		}
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder result = new StringBuilder();
+		if(function) {
+			result.append('(');
+		}
+		for(int i = 0; i < pointerLevel; ++i) {
+			result.append('*');
+		}
+		result.append(name);
+		if(tableSizes != null) {
+			for(int i = 0; i < tableSizes.length; ++i) {
+				result.append('[').append(tableSizes[i]).append(']');
+			}
+		}
+		if(function) {
+			result.append(")()");
+		}
+		//insert appropriate amount of spaces to format the output corrently
+		int nameLength = result.length();
+		result.append(' ');//at least one space is a must
+		for(int i = 1; i < NAME_LENGTH - nameLength; ++i) {//we start from i=1 because one space is already added
+			result.append(' ');
+		}
+		result.append(type);
+		nameLength = result.length();
+		for(int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) {
+			result.append(' ');
+		}
+		if(value instanceof Character) {
+			result.append(" = ").append((int)((Character)value).charValue());
+		} else {
+			result.append(" = ").append(value != null ? value.toString() : "null");
+		}
+		return result.toString();
+	}
+}

+ 201 - 0
engine/src/blender/com/jme3/scene/plugins/blender/data/FileBlockHeader.java

@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.data;
+
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+
+/**
+ * A class that holds the header data of a file block. The file block itself is not implemented. This class holds its
+ * start position in the stream and using this the structure can fill itself with the proper data.
+ * @author Marcin Roguski
+ */
+public class FileBlockHeader {
+	public static final int	BLOCK_TE00	= 'T' << 24 | 'E' << 16;					//TE00
+	public static final int	BLOCK_ME00	= 'M' << 24 | 'E' << 16;					//ME00
+	public static final int	BLOCK_SR00	= 'S' << 24 | 'R' << 16;					//SR00
+	public static final int	BLOCK_CA00	= 'C' << 24 | 'A' << 16;					//CA00
+	public static final int	BLOCK_LA00	= 'L' << 24 | 'A' << 16;					//LA00
+	public static final int	BLOCK_OB00	= 'O' << 24 | 'B' << 16;					//OB00
+	public static final int	BLOCK_MA00	= 'M' << 24 | 'A' << 16;					//MA00
+	public static final int	BLOCK_SC00	= 'S' << 24 | 'C' << 16;					//SC00
+	public static final int	BLOCK_WO00	= 'W' << 24 | 'O' << 16;					//WO00
+	public static final int	BLOCK_TX00	= 'T' << 24 | 'X' << 16;					//TX00
+	public static final int	BLOCK_IP00	= 'I' << 24 | 'P' << 16;					//IP00
+	public static final int	BLOCK_AC00	= 'A' << 24 | 'C' << 16;					//AC00
+
+	public static final int	BLOCK_GLOB	= 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B';	//GLOB
+	public static final int	BLOCK_REND	= 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D';	//REND
+	public static final int	BLOCK_DATA	= 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A';	//DATA
+	public static final int	BLOCK_DNA1	= 'D' << 24 | 'N' << 16 | 'A' << 8 | '1';	//DNA1
+	public static final int	BLOCK_ENDB	= 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B';	//ENDB
+
+	/** Identifier of the file-block [4 bytes]. */
+	private int				code;
+	/** Total length of the data after the file-block-header [4 bytes]. */
+	private int				size;
+	/**
+	 * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer
+	 * size)].
+	 */
+	private long			oldMemoryAddress;
+	/** Index of the SDNA structure [4 bytes]. */
+	private int				sdnaIndex;
+	/** Number of structure located in this file-block [4 bytes]. */
+	private int				count;
+	/** Start position of the block's data in the stream. */
+	private int				blockPosition;
+
+	/**
+	 * Constructor. Loads the block header from the given stream during instance creation.
+	 * @param inputStream
+	 *        the stream we read the block header from
+	 * @param dataRepository
+	 *        the data repository
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the pointer size is neither 4 nor 8
+	 */
+	public FileBlockHeader(BlenderInputStream inputStream, DataRepository dataRepository) throws BlenderFileException {
+		inputStream.alignPosition(4);
+		code = inputStream.readByte() << 24 | inputStream.readByte() << 16 |
+			   inputStream.readByte() << 8 | inputStream.readByte();
+		size = inputStream.readInt();
+		oldMemoryAddress = inputStream.readPointer();
+		sdnaIndex = inputStream.readInt();
+		count = inputStream.readInt();
+		blockPosition = inputStream.getPosition();
+		if(FileBlockHeader.BLOCK_DNA1 == code) {
+			dataRepository.setBlockData(new DnaBlockData(inputStream, dataRepository));
+		} else {
+			inputStream.setPosition(blockPosition + size);
+			dataRepository.addFileBlockHeader(Long.valueOf(oldMemoryAddress), this);
+		}
+	}
+
+	/**
+	 * This method returns the structure described by the header filled with appropriate data.
+	 * @param dataRepository
+	 *        the data repository
+	 * @return structure filled with data
+	 * @throws BlenderFileException
+	 */
+	public Structure getStructure(DataRepository dataRepository) throws BlenderFileException {
+		dataRepository.getInputStream().setPosition(blockPosition);
+		Structure structure = dataRepository.getDnaBlockData().getStructure(sdnaIndex);
+		structure.fill(dataRepository.getInputStream());
+		return structure;
+	}
+
+	/**
+	 * This method returns the code of this data block.
+	 * @return the code of this data block
+	 */
+	public int getCode() {
+		return code;
+	}
+
+	/**
+	 * This method returns the size of the data stored in this block.
+	 * @return the size of the data stored in this block
+	 */
+	public int getSize() {
+		return size;
+	}
+
+	/**
+	 * This method returns the memory address.
+	 * @return the memory address
+	 */
+	public long getOldMemoryAddress() {
+		return oldMemoryAddress;
+	}
+
+	/**
+	 * This method returns the sdna index.
+	 * @return the sdna index
+	 */
+	public int getSdnaIndex() {
+		return sdnaIndex;
+	}
+
+	/**
+	 * This data returns the number of structure stored in the data block after this header.
+	 * @return the number of structure stored in the data block after this header
+	 */
+	public int getCount() {
+		return count;
+	}
+
+	/**
+	 * This method returns the start position of the data block in the blend file stream.
+	 * @return the start position of the data block
+	 */
+	public int getBlockPosition() {
+		return blockPosition;
+	}
+
+	/**
+	 * This method indicates if the block is the last block in the file.
+	 * @return true if this block is the last one in the file nad false otherwise
+	 */
+	public boolean isLastBlock() {
+		return FileBlockHeader.BLOCK_ENDB == code;
+	}
+
+	/**
+	 * This method indicates if the block is the SDNA block.
+	 * @return true if this block is the SDNA block and false otherwise
+	 */
+	public boolean isDnaBlock() {
+		return FileBlockHeader.BLOCK_DNA1 == code;
+	}
+
+	@Override
+	public String toString() {
+		return "FILE BLOCK HEADER [" + this.codeToString(code) + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";
+	}
+
+	/**
+	 * This method transforms the coded bloch id into a string value.
+	 * @param code
+	 *        the id of the block
+	 * @return the string value of the block id
+	 */
+	protected String codeToString(int code) {
+		char c1 = (char)((code & 0xFF000000) >> 24);
+		char c2 = (char)((code & 0xFF0000) >> 16);
+		char c3 = (char)((code & 0xFF00) >> 8);
+		char c4 = (char)(code & 0xFF);
+		return String.valueOf(c1) + c2 + c3 + c4;
+	}
+}

+ 311 - 0
engine/src/blender/com/jme3/scene/plugins/blender/data/Structure.java

@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.data;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+/**
+ * A class representing a single structure in the file.
+ * @author Marcin Roguski
+ */
+public class Structure implements Cloneable {
+	/** The data repository. */
+	private DataRepository	dataRepository;
+	/** The address of the block that fills the structure. */
+	private transient Long	oldMemoryAddress;
+	/** The type of the structure. */
+	private String			type;
+	/**
+	 * The fields of the structure. Each field consists of a pair: name-type.
+	 */
+	private Field[]			fields;
+
+	/**
+	 * Constructor that copies the data of the structure.
+	 * @param structure
+	 *        the structure to copy.
+	 * @param dataRepository
+	 *        the data repository of the structure
+	 * @throws CloneNotSupportedException
+	 *         this exception should never be thrown
+	 */
+	private Structure(Structure structure, DataRepository dataRepository) throws CloneNotSupportedException {
+		type = structure.type;
+		fields = new Field[structure.fields.length];
+		for(int i = 0; i < fields.length; ++i) {
+			fields[i] = (Field)structure.fields[i].clone();
+		}
+		this.dataRepository = dataRepository;
+		this.oldMemoryAddress = structure.oldMemoryAddress;
+	}
+
+	/**
+	 * Constructor. Loads the structure from the given stream during instance creation.
+	 * @param inputStream
+	 *        the stream we read the structure from
+	 * @param names
+	 *        the names from which the name of structure and its fields will be taken
+	 * @param types
+	 *        the names of types for the structure
+	 * @param dataRepository
+	 *        the data repository
+	 * @throws BlenderFileException
+	 *         this exception occurs if the amount of fields, defined in the file, is negative
+	 */
+	public Structure(BlenderInputStream inputStream, String[] names, String[] types, DataRepository dataRepository) throws BlenderFileException {
+		int nameIndex = inputStream.readShort();
+		type = types[nameIndex];
+		this.dataRepository = dataRepository;
+		int fieldsAmount = inputStream.readShort();
+		if(fieldsAmount < 0) {
+			throw new BlenderFileException("The amount of fields of " + this.type + " structure cannot be negative!");
+		}
+		if(fieldsAmount > 0) {
+			fields = new Field[fieldsAmount];
+			for(int i = 0; i < fieldsAmount; ++i) {
+				int typeIndex = inputStream.readShort();
+				nameIndex = inputStream.readShort();
+				fields[i] = new Field(names[nameIndex], types[typeIndex], dataRepository);
+			}
+		}
+		this.oldMemoryAddress = Long.valueOf(-1L);
+	}
+
+	/**
+	 * This method fills the structure with data.
+	 * @param inputStream
+	 *        the stream we read data from, its read cursor should be placed at the start position of the data for the
+	 *        structure
+	 * @throws BlenderFileException
+	 *         an exception is thrown when the blend file is somehow invalid or corrupted
+	 */
+	public void fill(BlenderInputStream inputStream) throws BlenderFileException {
+		int position = inputStream.getPosition();
+		inputStream.setPosition(position - 8 - inputStream.getPointerSize());
+		this.oldMemoryAddress = Long.valueOf(inputStream.readPointer());
+		inputStream.setPosition(position);
+		for(Field field : fields) {
+			field.fill(inputStream);
+		}
+	}
+
+	/**
+	 * This method returns the value of the filed with a given name.
+	 * @param fieldName
+	 *        the name of the field
+	 * @return the value of the field or null if no field with a given name is found
+	 */
+	public Object getFieldValue(String fieldName) {
+		for(Field field : fields) {
+			if(field.name.equalsIgnoreCase(fieldName)) {
+				return field.value;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * This method returns the value of the filed with a given name. The structure is considered to have flat fields
+	 * only (no substructures).
+	 * @param fieldName
+	 *        the name of the field
+	 * @return the value of the field or null if no field with a given name is found
+	 */
+	public Object getFlatFieldValue(String fieldName) {
+		for(Field field : fields) {
+			Object value = field.value;
+			if(field.name.equalsIgnoreCase(fieldName)) {
+				return value;
+			} else if(value instanceof Structure) {
+				value = ((Structure)value).getFlatFieldValue(fieldName);
+				if(value != null) {//we can compare references here, since we use one static object as a NULL field value
+					return value;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * This methos should be used on structures that are of a 'ListBase' type. It creates a List of structures that are
+	 * held by this structure within the blend file.
+	 * @param dataRepository
+	 *        the data repository
+	 * @return a list of filled structures
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blend file structure is somehow invalid or corrupted
+	 * @throws IllegalArgumentException
+	 *         this exception is thrown if the type of the structure is not 'ListBase'
+	 */
+	public List<Structure> evaluateListBase(DataRepository dataRepository) throws BlenderFileException {
+		if(!"ListBase".equals(this.type)) {
+			throw new IllegalStateException("This structure is not of type: 'ListBase'");
+		}
+		Pointer first = (Pointer)this.getFieldValue("first");
+		Pointer last = (Pointer)this.getFieldValue("last");
+		long currentAddress = 0;
+		long lastAddress = last.getOldMemoryAddress();
+		List<Structure> result = new LinkedList<Structure>();
+		while(currentAddress != lastAddress) {
+			currentAddress = first.getOldMemoryAddress();
+			Structure structure = first.fetchData(dataRepository.getInputStream()).get(0);
+			result.add(structure);
+			first = (Pointer)structure.getFlatFieldValue("next");
+		}
+		return result;
+	}
+
+	/**
+	 * This method returns the type of the structure.
+	 * @return the type of the structure
+	 */
+	public String getType() {
+		return type;
+	}
+
+	/**
+	 * This method returns the amount of fields for the current structure.
+	 * @return the amount of fields for the current structure
+	 */
+	public int getFieldsAmount() {
+		return fields.length;
+	}
+
+	/**
+	 * This method returns the field name of the given index.
+	 * @param fieldIndex
+	 *        the index of the field
+	 * @return the field name of the given index
+	 */
+	public String getFieldName(int fieldIndex) {
+		return fields[fieldIndex].name;
+	}
+
+	/**
+	 * This method returns the field type of the given index.
+	 * @param fieldIndex
+	 *        the index of the field
+	 * @return the field type of the given index
+	 */
+	public String getFieldType(int fieldIndex) {
+		return fields[fieldIndex].type;
+	}
+
+	/**
+	 * This method returns the address of the structure. The strucutre should be filled with data otherwise an exception
+	 * is thrown.
+	 * @return the address of the feature stored in this structure
+	 */
+	public Long getOldMemoryAddress() {
+		if(oldMemoryAddress.longValue() == -1L) {
+			throw new IllegalStateException("Call the 'fill' method and fill the structure with data first!");
+		}
+		return oldMemoryAddress;
+	}
+
+	/**
+	 * This method returns the name of the structure. If the structure has an ID field then the name is returned.
+	 * Otherwise the name does not exists and the method returns null.
+	 * @return the name of the structure read from the ID field or null
+	 */
+	public String getName() {
+		Structure id = (Structure)this.getFieldValue("ID");
+		return id == null ? null : id.getFieldValue("name").toString().substring(2);//blender adds 2-charactes as a name prefix
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n");
+		for(int i = 0; i < fields.length; ++i) {
+			result.append(fields[i].toString()).append('\n');
+		}
+		return result.append('}').toString();
+	}
+
+	@Override
+	public Object clone() throws CloneNotSupportedException {
+		return new Structure(this, dataRepository);
+	}
+
+	/**
+	 * This enum enumerates all known data types that can be found in the blend file.
+	 * @author Marcin Roguski
+	 */
+	/*package*/static enum DataType {
+		CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER;
+
+		/** The map containing the known primary types. */
+		private static final Map<String, DataType>	PRIMARY_TYPES	= new HashMap<String, DataType>(10);
+		static {
+			PRIMARY_TYPES.put("char", CHARACTER);
+			PRIMARY_TYPES.put("uchar", CHARACTER);
+			PRIMARY_TYPES.put("short", SHORT);
+			PRIMARY_TYPES.put("ushort", SHORT);
+			PRIMARY_TYPES.put("int", INTEGER);
+			PRIMARY_TYPES.put("long", LONG);
+			PRIMARY_TYPES.put("ulong", LONG);
+			PRIMARY_TYPES.put("float", FLOAT);
+			PRIMARY_TYPES.put("double", DOUBLE);
+			PRIMARY_TYPES.put("void", VOID);
+		}
+
+		/**
+		 * This method returns the data type that is appropriate to the given type name. WARNING! The type recognition
+		 * is case sensitive!
+		 * @param type
+		 *        the type name of the data
+		 * @param dataRepository
+		 *        the data repository
+		 * @return appropriate enum value to the given type name
+		 * @throws BlenderFileException
+		 *         this exception is thrown if the given type name does not exist in the blend file
+		 */
+		public static DataType getDataType(String type, DataRepository dataRepository) throws BlenderFileException {
+			DataType result = PRIMARY_TYPES.get(type);
+			if(result != null) {
+				return result;
+			}
+			if(dataRepository.getDnaBlockData().hasStructure(type)) {
+				return STRUCTURE;
+			}
+			throw new BlenderFileException("Unknown data type: " + type);
+		}
+	}
+}

+ 74 - 0
engine/src/blender/com/jme3/scene/plugins/blender/exception/BlenderFileException.java

@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.exception;
+
+/**
+ * This exception is thrown when blend file data is somehow invalid.
+ * @author Marcin Roguski
+ */
+public class BlenderFileException extends Exception {
+	private static final long	serialVersionUID	= 7573482836437866767L;
+
+	/**
+	 * Constructor. Creates an exception with no description.
+	 */
+	public BlenderFileException() {}
+
+	/**
+	 * Constructor. Creates an exception containing the given message.
+	 * @param message
+	 *        the message describing the problem that occured
+	 */
+	public BlenderFileException(String message) {
+		super(message);
+	}
+
+	/**
+	 * Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then.
+	 * @param throwable
+	 *        an exception/error that occured
+	 */
+	public BlenderFileException(Throwable throwable) {
+		super(throwable);
+	}
+
+	/**
+	 * Constructor. Creates an exception with both a message and stacktrace.
+	 * @param message
+	 *        the message describing the problem that occured
+	 * @param throwable
+	 *        an exception/error that occured
+	 */
+	public BlenderFileException(String message, Throwable throwable) {
+		super(message, throwable);
+	}
+}

+ 132 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/ArmatureHelper.java

@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.animation.BoneTrack;
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.structures.BezierCurve;
+import com.jme3.scene.plugins.blender.structures.Ipo;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+
+/**
+ * This class defines the methods to calculate certain aspects of animation and armature functionalities.
+ * @author Marcin Roguski
+ */
+public class ArmatureHelper extends com.jme3.scene.plugins.blender.helpers.v249.ArmatureHelper {
+	private static final Logger				LOGGER			= Logger.getLogger(ArmatureHelper.class.getName());
+	
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ArmatureHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+	
+	@Override
+	public BoneTrack[] getTracks(Structure actionStructure, DataRepository dataRepository, String objectName, String animationName) throws BlenderFileException {
+		if(blenderVersion<250) {
+			return super.getTracks(actionStructure, dataRepository, objectName, animationName);
+		}
+		LOGGER.log(Level.INFO, "Getting tracks!");
+		int fps = dataRepository.getBlenderKey().getFps();
+		int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, animationName);
+		Structure groups = (Structure)actionStructure.getFieldValue("groups");
+		List<Structure> actionGroups = groups.evaluateListBase(dataRepository);//bActionGroup
+		if(actionGroups != null && actionGroups.size() > 0 && (bonesMap == null || bonesMap.size() == 0)) {
+			throw new IllegalStateException("No bones found! Cannot proceed to calculating tracks!");
+		}
+		
+		List<BoneTrack> tracks = new ArrayList<BoneTrack>();
+		for(Structure actionGroup : actionGroups) {
+			String name = actionGroup.getFieldValue("name").toString();
+			Integer boneIndex = bonesMap.get(name);
+			if(boneIndex != null) {
+				List<Structure> channels = ((Structure)actionGroup.getFieldValue("channels")).evaluateListBase(dataRepository);
+				BezierCurve[] bezierCurves = new BezierCurve[channels.size()];
+				int channelCounter = 0;
+				for(Structure c : channels) {
+					//reading rna path first
+					BlenderInputStream bis = dataRepository.getInputStream();
+					int currentPosition = bis.getPosition();
+					Pointer pRnaPath = (Pointer) c.getFieldValue("rna_path");
+					FileBlockHeader dataFileBlock = dataRepository.getFileBlock(pRnaPath.getOldMemoryAddress());
+					bis.setPosition(dataFileBlock.getBlockPosition());
+					String rnaPath = bis.readString();
+					bis.setPosition(currentPosition);
+					int arrayIndex = ((Number)c.getFieldValue("array_index")).intValue();
+					int type = this.getCurveType(rnaPath, arrayIndex);
+
+					Pointer pBezTriple = (Pointer)c.getFieldValue("bezt");
+					List<Structure> bezTriples = pBezTriple.fetchData(dataRepository.getInputStream());
+					bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2);
+				}
+
+				Ipo ipo = new Ipo(bezierCurves);
+				tracks.add(ipo.calculateTrack(boneIndex.intValue(), animationFrames[0], animationFrames[1], fps));
+			}
+		}
+		return tracks.toArray(new BoneTrack[tracks.size()]);
+	}
+	
+	/**
+	 * This method parses the information stored inside the curve rna path and returns the proper type
+	 * of the curve.
+	 * @param rnaPath the curve's rna path
+	 * @param arrayIndex the array index of the stored data
+	 * @return the type of the curve
+	 */
+	protected int getCurveType(String rnaPath, int arrayIndex) {
+		if(rnaPath.endsWith(".location")) {
+			return Ipo.AC_LOC_X + arrayIndex;
+		}
+		if(rnaPath.endsWith(".rotation_quaternion")) {
+			return Ipo.AC_QUAT_W + arrayIndex;
+		}
+		if(rnaPath.endsWith(".scale")) {
+			return Ipo.AC_SIZE_X + arrayIndex;
+		}
+		throw new IllegalStateException("Unknown curve rna path: " + rnaPath);
+	}
+}

+ 51 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/CameraHelper.java

@@ -0,0 +1,51 @@
+package com.jme3.scene.plugins.blender.helpers;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+
+/**
+ * A class that is used in light calculations.
+ * @author Marcin Roguski
+ */
+public class CameraHelper extends com.jme3.scene.plugins.blender.helpers.v249.CameraHelper {
+	private static final Logger			LOGGER		= Logger.getLogger(CameraHelper.class.getName());
+	
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public CameraHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+	
+	@Override
+	public Camera toCamera(Structure structure) throws BlenderFileException {
+		if(blenderVersion<250) {
+			return super.toCamera(structure);
+		}
+		Camera result = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
+		int type = ((Number)structure.getFieldValue("type")).intValue();
+		if(type != 0 && type != 1) {
+			LOGGER.log(Level.WARNING, "Unknown camera type: " + type + ". Perspective camera is being used!");
+			type = 0;
+		}
+		//type==0 - perspective; type==1 - orthographic; perspective is used as default
+		result.setParallelProjection(type == 1);
+		float aspect = 0;
+		float clipsta = ((Number)structure.getFieldValue("clipsta")).floatValue();
+		float clipend = ((Number)structure.getFieldValue("clipend")).floatValue();
+		if(type == 0) {
+			aspect = ((Number)structure.getFieldValue("lens")).floatValue();
+		} else {
+			aspect = ((Number)structure.getFieldValue("ortho_scale")).floatValue();
+		}
+		result.setFrustumPerspective(45, aspect, clipsta, clipend);
+		return result;
+	}
+}

+ 37 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/ConstraintHelper.java

@@ -0,0 +1,37 @@
+package com.jme3.scene.plugins.blender.helpers;
+
+import java.util.logging.Logger;
+
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+
+/**
+ * This class should be used for constraint calculations.
+ * @author Marcin Roguski
+ */
+public class ConstraintHelper extends com.jme3.scene.plugins.blender.helpers.v249.ConstraintHelper {
+	private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName());
+	
+	/**
+	 * Helper constructor. It's main task is to generate the affection functions. These functions are common to all
+	 * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall
+	 * consider refactoring. The constructor parses the given blender version and stores the result. Some
+	 * functionalities may differ in different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ConstraintHelper(String blenderVersion, DataRepository dataRepository) {
+		super(blenderVersion, dataRepository);
+	}
+	
+	@Override
+	public void loadConstraints(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
+		if(blenderVersion<250) {
+			super.loadConstraints(objectStructure, dataRepository);
+		} else {
+			LOGGER.warning("Loading of constraints not yet implemented for version 2.5x !");
+			//TODO: to implement
+		}
+	}
+}

+ 17 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/CurvesHelper.java

@@ -0,0 +1,17 @@
+package com.jme3.scene.plugins.blender.helpers;
+
+/**
+ * A class that is used in mesh calculations.
+ * @author Marcin Roguski
+ */
+public class CurvesHelper extends com.jme3.scene.plugins.blender.helpers.v249.CurvesHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public CurvesHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 18 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/IpoHelper.java

@@ -0,0 +1,18 @@
+package com.jme3.scene.plugins.blender.helpers;
+
+/**
+ * This class helps to compute values from interpolation curves for features like animation or constraint influence. The
+ * curves are 3rd degree bezier curves.
+ * @author Marcin Roguski
+ */
+public class IpoHelper extends com.jme3.scene.plugins.blender.helpers.v249.IpoHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public IpoHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 48 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/LightHelper.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers;
+
+/**
+ * A class that is used in light calculations.
+ * @author Marcin Roguski
+ */
+public class LightHelper extends com.jme3.scene.plugins.blender.helpers.v249.LightHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public LightHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 44 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/MaterialHelper.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers;
+
+public class MaterialHelper extends com.jme3.scene.plugins.blender.helpers.v249.MaterialHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public MaterialHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 48 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/MeshHelper.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers;
+
+/**
+ * A class that is used in mesh calculations.
+ * @author Marcin Roguski
+ */
+public class MeshHelper extends com.jme3.scene.plugins.blender.helpers.v249.MeshHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public MeshHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 48 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/ModifierHelper.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers;
+
+/**
+ * A class that is used in modifiers calculations.
+ * @author Marcin Roguski
+ */
+public class ModifierHelper extends com.jme3.scene.plugins.blender.helpers.v249.ModifierHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ModifierHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 51 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/NoiseHelper.java

@@ -0,0 +1,51 @@
+/*
+ *
+ * $Id: noise.c 14611 2008-04-29 08:24:33Z campbellbarton $
+ *
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+package com.jme3.scene.plugins.blender.helpers;
+
+/**
+ * Methods of this class are copied from blender 2.49 source code and modified so that they can be used in java. They are mostly NOT
+ * documented because they are not documented in blender's source code. If I find a proper description or discover what they actually do and
+ * what parameters mean - I shall describe such methods :) If anyone have some hint what these methods are doing please rite the proper
+ * javadoc documentation. These methods should be used to create generated textures.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class NoiseHelper extends com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper {
+	/**
+	 * Constructor. Stores the blender version number and loads the constants needed for computations.
+	 * 
+	 * @param blenderVersion
+	 *            the number of blender version
+	 */
+	public NoiseHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 48 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/ObjectHelper.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers;
+
+/**
+ * A class that is used in object calculations.
+ * @author Marcin Roguski
+ */
+public class ObjectHelper extends com.jme3.scene.plugins.blender.helpers.v249.ObjectHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ObjectHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 17 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/ParticlesHelper.java

@@ -0,0 +1,17 @@
+package com.jme3.scene.plugins.blender.helpers;
+
+/**
+ * This class helps to import the special effects from blender file.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class ParticlesHelper extends com.jme3.scene.plugins.blender.helpers.v249.ParticlesHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ParticlesHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+}

+ 82 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/TextureHelper.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers;
+
+import java.util.logging.Logger;
+
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;
+import com.jme3.texture.Texture;
+
+/**
+ * A class that is used in texture calculations.
+ * @author Marcin Roguski
+ */
+public class TextureHelper extends com.jme3.scene.plugins.blender.helpers.v249.TextureHelper {
+	private static final Logger	LOGGER			= Logger.getLogger(TextureHelper.class.getName());
+	public static final int		TEX_POINTDENSITY	= 14;
+	public static final int		TEX_VOXELDATA		= 15;
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public TextureHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+	
+	@Override
+	public Texture getTexture(Structure tex, DataRepository dataRepository) throws BlenderFileException {
+		if(blenderVersion<250) {
+			return super.getTexture(tex, dataRepository);
+		}
+		Texture result = (Texture) dataRepository.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+		if (result != null) {
+			return result;
+		}
+		int type = ((Number)tex.getFieldValue("type")).intValue();
+		switch(type) {
+			case TEX_POINTDENSITY:
+				LOGGER.warning("Point density texture loading currently not supported!");
+				break;
+			case TEX_VOXELDATA:
+				LOGGER.warning("Voxel data texture loading currently not supported!");
+				break;
+			default:
+				result = super.getTexture(tex, dataRepository);
+		}
+		return result;
+	}
+}

+ 370 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ArmatureHelper.java

@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.structures.Ipo;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+/**
+ * This class defines the methods to calculate certain aspects of animation and armature functionalities.
+ * @author Marcin Roguski
+ */
+public class ArmatureHelper extends AbstractBlenderHelper {
+	private static final Logger				LOGGER			= Logger.getLogger(ArmatureHelper.class.getName());
+
+	/**
+	 * The map of the bones. Maps a bone name to its index in the armature. Should be cleared after the object had been
+	 * read. TODO: probably bones can have identical names in different armatures
+	 */
+	protected Map<String, Integer>				bonesMap		= new HashMap<String, Integer>();
+	/** A map of bones and their old memory addresses. */
+	protected Map<Bone, Long>					bonesOMAs		= new HashMap<Bone, Long>();
+	/** This list contains bones hierarchy and their matrices. It is later converted into jme bones. */
+	protected List<BoneTransformationData>		boneDataRoots	= new ArrayList<BoneTransformationData>();
+	
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ArmatureHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	/**
+	 * This method returns the old memory address of a bone. If the bone does not exist in the blend file - zero is
+	 * returned.
+	 * @param bone
+	 *        the bone whose old memory address we seek
+	 * @return the old memory address of the given bone
+	 */
+	public Long getBoneOMA(Bone bone) {
+		Long result = bonesOMAs.get(bone);
+		if(result == null) {
+			result = Long.valueOf(0);
+		}
+		return result;
+	}
+
+	/**
+	 * This method reads the bones and returns an empty skeleton. Bones should be assigned later.
+	 * @param structure
+	 *        armature structure
+	 * @param dataRepository
+	 *        the data repository
+	 * @return an empty skeleton, bones are stored within the helper object
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blender file is somehow corrupted
+	 */
+	public Skeleton toArmature(Structure structure, DataRepository dataRepository) throws BlenderFileException {
+		LOGGER.log(Level.INFO, "Converting structure to Armature!");
+		Structure bonebase = (Structure)structure.getFieldValue("bonebase");
+		List<Structure> bonesStructures = bonebase.evaluateListBase(dataRepository);
+		for(Structure boneStructure : bonesStructures) {
+			BoneTransformationData rootBoneTransformationData = this.readBoneAndItsChildren(boneStructure, null, dataRepository);
+			boneDataRoots.add(rootBoneTransformationData);
+		}
+		return new Skeleton();//bones are assigned later
+	}
+
+	/**
+	 * This method returns a map where the key is the object's group index that is used by a bone and the key is the
+	 * bone index in the armature.
+	 * @param poseStructure
+	 *        a bPose structure of the object
+	 * @return bone group-to-index map
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blender file is somehow corrupted
+	 */
+	public Map<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, DataRepository dataRepository) throws BlenderFileException {
+		Map<Integer, Integer> result = null;
+		if(bonesMap != null && bonesMap.size() != 0) {
+			result = new HashMap<Integer, Integer>();
+			List<Structure> deformGroups = defBaseStructure.evaluateListBase(dataRepository);//bDeformGroup
+			int groupIndex = 0;//TODO: consider many armatures attached to one object in the future !!!
+			for(Structure deformGroup : deformGroups) {
+				String deformGroupName = deformGroup.getFieldValue("name").toString();
+				Integer boneIndex = bonesMap.get(deformGroupName);
+				if(boneIndex != null) {
+					result.put(Integer.valueOf(groupIndex), boneIndex);
+				}
+				++groupIndex;
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * This method reads the tracks of the armature object.
+	 * @param actionStructure
+	 * @param dataRepository
+	 *        the data repository
+	 * @param objectName
+	 *        the name of animation owner
+	 * @param animationName
+	 *        the name of the animation
+	 * @return a list of tracks for the armature
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blender file is somehow corrupted
+	 */
+	public BoneTrack[] getTracks(Structure actionStructure, DataRepository dataRepository, String objectName, String animationName) throws BlenderFileException {
+		LOGGER.log(Level.INFO, "Getting tracks!");
+		IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class);
+		int fps = dataRepository.getBlenderKey().getFps();
+		int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, animationName);
+		Structure chanbase = (Structure)actionStructure.getFieldValue("chanbase");
+		List<Structure> actionChannels = chanbase.evaluateListBase(dataRepository);//bActionChannel
+		if(actionChannels != null && actionChannels.size() > 0 && (bonesMap == null || bonesMap.size() == 0)) {
+			throw new IllegalStateException("No bones found! Cannot proceed to calculating tracks!");
+		}
+		List<BoneTrack> tracks = new ArrayList<BoneTrack>();
+		for(Structure bActionChannel : actionChannels) {
+			String name = bActionChannel.getFieldValue("name").toString();
+			Integer boneIndex = bonesMap.get(name);
+			if(boneIndex != null) {
+				Pointer p = (Pointer)bActionChannel.getFieldValue("ipo");
+				if(!p.isNull()) {
+					Structure ipoStructure = p.fetchData(dataRepository.getInputStream()).get(0);
+					Ipo ipo = ipoHelper.createIpo(ipoStructure, dataRepository);
+					tracks.add(ipo.calculateTrack(boneIndex.intValue(), animationFrames[0], animationFrames[1], fps));
+				}
+			}
+		}
+		return tracks.toArray(new BoneTrack[tracks.size()]);
+	}
+
+	/**
+	 * This bone returns transformation matrix of the bone that is relative to
+	 * its armature object.
+	 * @param boneStructure the bone's structure
+	 * @return bone's transformation matrix in armature space
+	 */
+	@SuppressWarnings("unchecked")
+	protected Matrix4f getArmatureMatrix(Structure boneStructure) {
+		DynamicArray<Number> boneMat = (DynamicArray<Number>)boneStructure.getFieldValue("arm_mat");
+		Matrix4f m = new Matrix4f();
+		for(int i = 0; i < 4; ++i) {
+			for(int j = 0; j < 4; ++j) {
+				m.set(i, j, boneMat.get(j, i).floatValue());
+			}
+		}
+		return m;
+	}
+
+	/**
+	 * This method reads the bone with its children.
+	 * @param boneStructure
+	 *        a structure containing the bone data
+	 * @param parent
+	 *        the bone parent; if null then we read the root bone
+	 * @param dataRepository
+	 *        the data repository
+	 * @return the bone transformation data; contains bone chierarchy and the bone's matrices
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blender file is somehow corrupted
+	 */
+	@SuppressWarnings("unchecked")
+	protected BoneTransformationData readBoneAndItsChildren(Structure boneStructure, BoneTransformationData parent, DataRepository dataRepository) throws BlenderFileException {
+		String name = boneStructure.getFieldValue("name").toString();
+		Bone bone = new Bone(name);
+		int bonesAmount = bonesOMAs.size();
+		bonesOMAs.put(bone, boneStructure.getOldMemoryAddress());
+		if(bonesAmount == bonesOMAs.size()) {
+			throw new IllegalStateException("Two bones has the same hash value and thereforw a bone was overriden in the bones<->OMA map! Improve the hash algorithm!");
+		}
+		Matrix4f boneArmatureMatrix = this.getArmatureMatrix(boneStructure);
+		DynamicArray<Float> sizeArray = (DynamicArray<Float>) boneStructure.getFieldValue("size");
+		Vector3f size = new Vector3f(sizeArray.get(0), sizeArray.get(1), sizeArray.get(2));
+		BoneTransformationData boneTransformationData = new BoneTransformationData(boneArmatureMatrix, size, bone, parent);
+		dataRepository.addLoadedFeatures(boneStructure.getOldMemoryAddress(), name, boneStructure, bone);
+
+		Structure childbase = (Structure)boneStructure.getFieldValue("childbase");
+		List<Structure> children = childbase.evaluateListBase(dataRepository);//Bone
+		for(Structure boneChild : children) {
+			this.readBoneAndItsChildren(boneChild, boneTransformationData, dataRepository);
+		}
+		return boneTransformationData;
+	}
+
+	/**
+	 * This method assigns transformations to the bone.
+	 * @param btd
+	 *        the bone data containing the bone we assign transformation to
+	 * @param additionalRootBoneTransformation
+	 *        additional bone transformation which indicates it's mesh parent and armature object transformations
+	 * @param boneList
+	 *        a list of all read bones
+	 */
+	protected void assignBonesMatrices(BoneTransformationData btd, Matrix4f additionalRootBoneTransformation, List<Bone> boneList) {
+		LOGGER.info("[" + btd.bone.getName() + "]  additionalRootBoneTransformation =\n" + additionalRootBoneTransformation);
+		Matrix4f totalInverseParentMatrix = btd.parent != null ? btd.parent.totalInverseBoneParentMatrix : Matrix4f.IDENTITY;
+		LOGGER.info("[" + btd.bone.getName() + "]  totalInverseParentMatrix =\n" + totalInverseParentMatrix);
+		Matrix4f restMatrix = additionalRootBoneTransformation.mult(btd.boneArmatureMatrix);
+		LOGGER.info("[" + btd.bone.getName() + "]  restMatrix =\n" + restMatrix);
+		btd.totalInverseBoneParentMatrix = restMatrix.clone().invert();
+		restMatrix = totalInverseParentMatrix.mult(restMatrix);
+		LOGGER.info("[" + btd.bone.getName() + "]  resultMatrix =\n" + restMatrix);
+		btd.bone.setBindTransforms(restMatrix.toTranslationVector(), restMatrix.toRotationQuat(), btd.size);
+		boneList.add(btd.bone);
+		bonesMap.put(btd.bone.getName(), Integer.valueOf(boneList.size() - 1));
+		if(btd.children != null && btd.children.size() > 0) {
+			for(BoneTransformationData child : btd.children) {
+				this.assignBonesMatrices(child, additionalRootBoneTransformation, boneList);
+				btd.bone.addChild(child.bone);
+			}
+		}
+	}
+
+	/**
+	 * This method returns bone transformation data for the bone of a given name.
+	 * @param boneName
+	 *        the name of the bone
+	 * @return bone's transformation data
+	 */
+	public BoneTransformationData getBoneTransformationDataRoot(int index) {
+		return boneDataRoots.get(index);
+	}
+
+	/**
+	 * This method returns the amount of bones transformations roots.
+	 * @return the amount of bones transformations roots
+	 */
+	public int getBoneTransformationDataRootsSize() {
+		return boneDataRoots.size();
+	}
+
+	/**
+	 * This class holds the data needed later for bone transformation calculation and to bind parent with children.
+	 * @author Marcin Roguski
+	 */
+	private static class BoneTransformationData {
+		/** Inverse matrix of bone's parent bone. */
+		private Matrix4f						totalInverseBoneParentMatrix;
+		/** Bone's matrix in armature's space. */
+		private Matrix4f						boneArmatureMatrix;
+		/** Bone's size (apparently it is held outside the transformation matrix. */
+		private Vector3f						size;
+		/** The bone the data applies to. */
+		private Bone							bone;
+		/** The parent of the above mentioned bone (not assigned yet). */
+		private BoneTransformationData			parent;
+		/** The children of the current bone. */
+		private List<BoneTransformationData>	children;
+
+		/**
+		 * Private constructor creates the object and assigns the given data.
+		 * @param boneArmatureMatrix
+		 *        the matrix of the current bone
+		 * @param size
+		 * 		  the bone's size
+		 * @param bone
+		 *        the current bone
+		 * @param parent
+		 *        the parent structure of the bone
+		 */
+		private BoneTransformationData(Matrix4f boneArmatureMatrix, Vector3f size, Bone bone, BoneTransformationData parent) {
+			this.boneArmatureMatrix = boneArmatureMatrix;
+			this.size = size;
+			this.bone = bone;
+			this.parent = parent;
+			this.children = new ArrayList<ArmatureHelper.BoneTransformationData>();
+			if(this.parent != null) {
+				this.parent.children.add(this);
+			}
+		}
+	}
+
+	/**
+	 * This method creates the whole bones structure. Assignes transformations to bones and combines children with
+	 * parents.
+	 * @param armatureOMA
+	 *        old memory address of bones' armature object
+	 * @param additionalRootBoneTransformation
+	 *        additional bone transformation which indicates it's mesh parent and armature object transformations
+	 * @return
+	 */
+	public Bone[] buildBonesStructure(Long armatureOMA, Matrix4f additionalRootBoneTransformation) {//TODO: uwzględnić wiele szkieletów
+		List<Bone> bones = new ArrayList<Bone>(boneDataRoots.size() + 1);
+		bones.add(new Bone(null));
+		for(BoneTransformationData btd : boneDataRoots) {
+			this.assignBonesMatrices(btd, additionalRootBoneTransformation, bones);
+		}
+		return bones.toArray(new Bone[bones.size()]);
+	}
+	
+	/**
+	 * This method assigns an immovable bone to vertices that have no bone assigned. They have the bone index with the
+	 * value -1.
+	 * @param immovableBoneIndex
+	 *        the ondex of immovable bone
+	 * @param meshes
+	 *        a list of meshes whose vertices will be assigned to immovable bone
+	 */
+	public void assignBoneToOrphanedVertices(byte immovableBoneIndex, Mesh[] meshes) {
+		//bone indices are common for all the object's meshes (vertex indices specify which are to be used)
+		VertexBuffer boneIndices = meshes[0].getBuffer(Type.BoneIndex);//common buffer to all the meshes
+		ByteBuffer data = (ByteBuffer)boneIndices.getData();
+		for(int i = 0; i < boneIndices.getNumElements(); ++i) {
+			if(data.get(i) == -1) {
+				data.put(i, immovableBoneIndex);
+			}
+		}
+	}
+
+	@Override
+	public void clearState() {
+		bonesMap.clear();
+		boneDataRoots.clear();
+	}
+}

+ 57 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CameraHelper.java

@@ -0,0 +1,57 @@
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+
+/**
+ * A class that is used in light calculations.
+ * @author Marcin Roguski
+ */
+public class CameraHelper extends AbstractBlenderHelper {
+	private static final Logger			LOGGER		= Logger.getLogger(CameraHelper.class.getName());
+	
+	protected static final int		DEFAULT_CAM_WIDTH			= 100;
+	protected static final int		DEFAULT_CAM_HEIGHT			= 100;
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public CameraHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	/**
+	 * This method reads the camera object.
+	 * @param structure the structure containing the camera data
+	 * @return the camera object
+	 * @throws BlenderFileException
+	 */
+	public Camera toCamera(Structure structure) throws BlenderFileException {
+		Camera result = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
+		int type = ((Number)structure.getFieldValue("type")).intValue();
+		if(type != 0 && type != 1) {
+			LOGGER.log(Level.WARNING, "Unknown camera type: " + type + ". Perspective camera is being used!");
+			type = 0;
+		}
+		//type==0 - perspective; type==1 - orthographic; perspective is used as default
+		result.setParallelProjection(type == 1);
+		float angle = ((Number)structure.getFieldValue("angle")).floatValue();
+		float aspect = 0;
+		float clipsta = ((Number)structure.getFieldValue("clipsta")).floatValue();
+		float clipend = ((Number)structure.getFieldValue("clipend")).floatValue();
+		if(type == 0) {
+			aspect = ((Number)structure.getFieldValue("lens")).floatValue();
+		} else {
+			aspect = ((Number)structure.getFieldValue("ortho_scale")).floatValue();
+		}
+		result.setFrustumPerspective(angle, aspect, clipsta, clipend);
+		return result;
+	}	
+}

+ 734 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ConstraintHelper.java

@@ -0,0 +1,734 @@
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneAnimation;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.structures.AbstractInfluenceFunction;
+import com.jme3.scene.plugins.blender.structures.Constraint;
+import com.jme3.scene.plugins.blender.structures.Constraint.Space;
+import com.jme3.scene.plugins.blender.structures.ConstraintType;
+import com.jme3.scene.plugins.blender.structures.Ipo;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+/**
+ * This class should be used for constraint calculations.
+ * @author Marcin Roguski
+ */
+public class ConstraintHelper extends AbstractBlenderHelper {
+	/**
+	 * A table containing implementations of influence functions for constraints. It should contain functions for
+	 * blender at least 249 and higher.
+	 */
+	protected static AbstractInfluenceFunction[]	influenceFunctions;
+
+	/**
+	 * Constraints stored for object with the given old memory address.
+	 */
+	protected Map<Long, Constraint[]>				constraints	= new HashMap<Long, Constraint[]>();
+
+	/**
+	 * Helper constructor. It's main task is to generate the affection functions. These functions are common to all
+	 * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall
+	 * consider refactoring. The constructor parses the given blender version and stores the result. Some
+	 * functionalities may differ in different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ConstraintHelper(String blenderVersion, DataRepository dataRepository) {
+		super(blenderVersion);
+		if(influenceFunctions == null) {
+			//TODO: synchronization
+			influenceFunctions = new AbstractInfluenceFunction[ConstraintType.getLastDefinedTypeValue() + 1];
+			//ACTION constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_ACTION.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_ACTION, dataRepository) {};
+
+			//CHILDOF constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_CHILDOF.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_CHILDOF, dataRepository) {};
+
+			//CLAMPTO constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_CLAMPTO.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_CLAMPTO, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					this.validateConstraintType(constraint.getData());
+					LOGGER.info(constraint.getName() + " not active! Curves not yet implemented!");//TODO: implement when curves are implemented
+				}
+			};
+
+			//DISTLIMIT constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_DISTLIMIT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_DISTLIMIT, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					Structure constraintStructure = constraint.getData();
+					this.validateConstraintType(constraintStructure);
+					Vector3f targetLocation = this.getTargetLocation(constraint);
+					BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint);
+					if(boneTrack != null) {
+						//TODO: target vertex group !!!
+						float dist = ((Number)constraintStructure.getFieldValue("dist")).floatValue();
+						int mode = ((Number)constraintStructure.getFieldValue("mode")).intValue();
+
+						int maxFrames = boneTrack.getTimes().length;
+						Vector3f[] translations = boneTrack.getTranslations();
+						for(int frame = 0; frame < maxFrames; ++frame) {
+							Vector3f v = translations[frame].subtract(targetLocation);
+							float currentDistance = v.length();
+							float influence = constraint.getIpo().calculateValue(frame);
+							float modifier = 0.0f;
+							switch(mode) {
+								case LIMITDIST_INSIDE:
+									if(currentDistance >= dist) {
+										modifier = (dist - currentDistance) / currentDistance;
+									}
+									break;
+								case LIMITDIST_ONSURFACE:
+									modifier = (dist - currentDistance) / currentDistance;
+									break;
+								case LIMITDIST_OUTSIDE:
+									if(currentDistance <= dist) {
+										modifier = (dist - currentDistance) / currentDistance;
+									}
+									break;
+								default:
+									throw new IllegalStateException("Unknown distance limit constraint mode: " + mode);
+							}
+							translations[frame].addLocal(v.multLocal(modifier * influence));
+						}
+						boneTrack.setKeyframes(boneTrack.getTimes(), translations, boneTrack.getRotations(), boneTrack.getScales());
+					}
+				}
+			};
+
+			//FOLLOWPATH constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_FOLLOWPATH.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_FOLLOWPATH, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					this.validateConstraintType(constraint.getData());
+					LOGGER.info(constraint.getName() + " not active! Curves not yet implemented!");//TODO: implement when curves are implemented
+				}
+			};
+
+			//KINEMATIC constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_KINEMATIC.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_KINEMATIC, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					Structure constraintStructure = constraint.getData();
+					this.validateConstraintType(constraintStructure);
+					/*Long boneOMA = constraint.getBoneOMA();
+					//IK solver is only attached to bones
+					Bone ownerBone = (Bone)dataRepository.getLoadedFeature(boneOMA, LoadedFeatureDataType.LOADED_FEATURE);
+
+					//get the target point
+					Object targetObject = this.getTarget(constraint, LoadedFeatureDataType.LOADED_FEATURE);
+					Vector3f pt = null;//Point Target
+					if(targetObject instanceof Bone) {
+						pt = ((Bone)targetObject).getModelSpacePosition();
+					} else if(targetObject instanceof Node) {
+						pt = ((Node)targetObject).getWorldTranslation();
+					} else if(targetObject instanceof Skeleton) {
+						Structure armatureNodeStructure = (Structure)this.getTarget(constraint, LoadedFeatureDataType.LOADED_STRUCTURE);
+						ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+						Transform transform = objectHelper.getTransformation(armatureNodeStructure);
+						pt = transform.getTranslation();
+					} else {
+						throw new IllegalStateException("Unknown target object type! Should be Node, Bone or Skeleton and there is: " + targetObject.getClass().getName());
+					}
+					//preparing data
+					int maxIterations = ((Number)constraintStructure.getFieldValue("iterations")).intValue();
+					CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation);
+					for(int i=0;i<bones.length;++i) {
+						System.out.println(Arrays.toString(bones[i].track.getTranslations()));
+						System.out.println(Arrays.toString(bones[i].track.getRotations()));
+						System.out.println("===============================");
+					}
+					Quaternion rotation = new Quaternion();
+					int maxFrames = bones[0].track.getTimes().length;//all tracks should have the same amount of frames
+
+					for(int frame = 0; frame < maxFrames; ++frame) {
+						float error = IK_SOLVER_ERROR;
+						int iteration = 0;
+						while(error >= IK_SOLVER_ERROR && iteration <= maxIterations) {
+							//rotating the bones
+							for(int i = 0; i < bones.length - 1; ++i) {
+								Vector3f pe = bones[i].getEndPoint();
+								Vector3f pc = bones[i + 1].getWorldTranslation().clone();
+
+								Vector3f peSUBpc = pe.subtract(pc).normalizeLocal();
+								Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal();
+
+								float theta = FastMath.acos(peSUBpc.dot(ptSUBpc));
+								Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal();
+								bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame);
+							}
+							error = pt.subtract(bones[0].getEndPoint()).length();
+							++iteration;
+						}
+						System.out.println("error = " + error + "   iterations = " + iteration);
+					}
+					
+					for(CalculationBone bone : bones) {
+						bone.applyCalculatedTracks();
+					}
+					
+					System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
+					for(int i=0;i<bones.length;++i) {
+						System.out.println(Arrays.toString(bones[i].track.getTranslations()));
+						System.out.println(Arrays.toString(bones[i].track.getRotations()));
+						System.out.println("===============================");
+					}*/
+				}
+
+				/**
+				 * This method returns bones used for rotation calculations.
+				 * @param bone
+				 *        the bone to which the constraint is applied
+				 * @param skeleton
+				 *        the skeleton owning the bone and its ancestors
+				 * @param boneAnimation
+				 *        the bone animation data that stores the traces for the skeleton's bones
+				 * @return a list of bones to imitate the bone's movement during IK solving
+				 */
+				private CalculationBone[] getBonesToCalculate(Bone bone, Skeleton skeleton, BoneAnimation boneAnimation) {
+					List<CalculationBone> bonesList = new ArrayList<CalculationBone>();
+					Bone currentBone = bone;
+					do {
+						int boneIndex = skeleton.getBoneIndex(currentBone);
+						for(int i = 0; i < boneAnimation.getTracks().length; ++i) {
+							if(boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) {
+								bonesList.add(new CalculationBone(currentBone, boneAnimation.getTracks()[i]));
+								break;
+							}
+						}
+						currentBone = currentBone.getParent();
+					} while(currentBone != null);
+					//attaching children
+					CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]);
+					for(int i = result.length - 1; i > 0; --i) {
+						result[i].attachChild(result[i - 1]);
+					}
+					return result;
+				}
+			};
+
+			//LOCKTRACK constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_LOCKTRACK.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_LOCKTRACK, dataRepository) {};
+
+			//LOCLIKE constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_LOCLIKE.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_LOCLIKE, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					Structure constraintData = constraint.getData();
+					this.validateConstraintType(constraintData);
+					BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint);
+					if(boneTrack != null) {
+						Vector3f targetLocation = this.getTargetLocation(constraint);
+						int flag = ((Number)constraintData.getFieldValue("flag")).intValue();
+						Vector3f[] translations = boneTrack.getTranslations();
+						int maxFrames = translations.length;
+						for(int frame = 0; frame < maxFrames; ++frame) {
+							Vector3f offset = Vector3f.ZERO;
+							if((flag & LOCLIKE_OFFSET) != 0) {//we add the original location to the copied location
+								offset = translations[frame].clone();
+							}
+
+							if((flag & LOCLIKE_X) != 0) {
+								translations[frame].x = targetLocation.x;
+								if((flag & LOCLIKE_X_INVERT) != 0) {
+									translations[frame].x = -translations[frame].x;
+								}
+							} else if((flag & LOCLIKE_Y) != 0) {
+								translations[frame].y = targetLocation.y;
+								if((flag & LOCLIKE_Y_INVERT) != 0) {
+									translations[frame].y = -translations[frame].y;
+								}
+							} else if((flag & LOCLIKE_Z) != 0) {
+								translations[frame].z = targetLocation.z;
+								if((flag & LOCLIKE_Z_INVERT) != 0) {
+									translations[frame].z = -translations[frame].z;
+								}
+							}
+							translations[frame].addLocal(offset);//TODO: ipo influence
+						}
+						boneTrack.setKeyframes(boneTrack.getTimes(), translations, boneTrack.getRotations(), boneTrack.getScales());
+					}
+				}
+			};
+
+			//LOCLIMIT constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_LOCLIMIT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_LOCLIMIT, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					Structure constraintStructure = constraint.getData();
+					this.validateConstraintType(constraintStructure);
+					BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint);
+					if(boneTrack != null) {
+						int flag = ((Number)constraintStructure.getFieldValue("flag")).intValue();
+						Vector3f[] translations = boneTrack.getTranslations();
+						int maxFrames = translations.length;
+						for(int frame = 0; frame < maxFrames; ++frame) {
+							float influence = constraint.getIpo().calculateValue(frame);
+							if((flag & LIMIT_XMIN) != 0) {
+								float xmin = ((Number)constraintStructure.getFieldValue("xmin")).floatValue();
+								if(translations[frame].x < xmin) {
+									translations[frame].x -= (translations[frame].x - xmin) * influence;
+								}
+							}
+							if((flag & LIMIT_XMAX) != 0) {
+								float xmax = ((Number)constraintStructure.getFieldValue("xmax")).floatValue();
+								if(translations[frame].x > xmax) {
+									translations[frame].x -= (translations[frame].x - xmax) * influence;
+								}
+							}
+							if((flag & LIMIT_YMIN) != 0) {
+								float ymin = ((Number)constraintStructure.getFieldValue("ymin")).floatValue();
+								if(translations[frame].y < ymin) {
+									translations[frame].y -= (translations[frame].y - ymin) * influence;
+								}
+							}
+							if((flag & LIMIT_YMAX) != 0) {
+								float ymax = ((Number)constraintStructure.getFieldValue("ymax")).floatValue();
+								if(translations[frame].y > ymax) {
+									translations[frame].y -= (translations[frame].y - ymax) * influence;
+								}
+							}
+							if((flag & LIMIT_ZMIN) != 0) {
+								float zmin = ((Number)constraintStructure.getFieldValue("zmin")).floatValue();
+								if(translations[frame].z < zmin) {
+									translations[frame].z -= (translations[frame].z - zmin) * influence;
+								}
+							}
+							if((flag & LIMIT_ZMAX) != 0) {
+								float zmax = ((Number)constraintStructure.getFieldValue("zmax")).floatValue();
+								if(translations[frame].z > zmax) {
+									translations[frame].z -= (translations[frame].z - zmax) * influence;
+								}
+							}//TODO: consider constraint space !!!
+						}
+						boneTrack.setKeyframes(boneTrack.getTimes(), translations, boneTrack.getRotations(), boneTrack.getScales());
+					}
+				}
+			};
+
+			//MINMAX constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_MINMAX.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_MINMAX, dataRepository) {};
+
+			//NULL constraint - does nothing
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_NULL.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_NULL, dataRepository) {};
+
+			//PYTHON constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_PYTHON.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_PYTHON, dataRepository) {};
+
+			//RIGIDBODYJOINT constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_RIGIDBODYJOINT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_RIGIDBODYJOINT, dataRepository) {};
+
+			//ROTLIKE constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_ROTLIKE.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_ROTLIKE, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					Structure constraintData = constraint.getData();
+					this.validateConstraintType(constraintData);
+					BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint);
+					if(boneTrack != null) {
+						Quaternion targetRotation = this.getTargetRotation(constraint);
+						int flag = ((Number)constraintData.getFieldValue("flag")).intValue();
+						float[] targetAngles = targetRotation.toAngles(null);
+						Quaternion[] rotations = boneTrack.getRotations();
+						int maxFrames = rotations.length;
+						for(int frame = 0; frame < maxFrames; ++frame) {
+							float[] angles = rotations[frame].toAngles(null);
+
+							Quaternion offset = Quaternion.IDENTITY;
+							if((flag & ROTLIKE_OFFSET) != 0) {//we add the original rotation to the copied rotation
+								offset = rotations[frame].clone();
+							}
+
+							if((flag & ROTLIKE_X) != 0) {
+								angles[0] = targetAngles[0];
+								if((flag & ROTLIKE_X_INVERT) != 0) {
+									angles[0] = -angles[0];
+								}
+							} else if((flag & ROTLIKE_Y) != 0) {
+								angles[1] = targetAngles[1];
+								if((flag & ROTLIKE_Y_INVERT) != 0) {
+									angles[1] = -angles[1];
+								}
+							} else if((flag & ROTLIKE_Z) != 0) {
+								angles[2] = targetAngles[2];
+								if((flag & ROTLIKE_Z_INVERT) != 0) {
+									angles[2] = -angles[2];
+								}
+							}
+							rotations[frame].fromAngles(angles).multLocal(offset);//TODO: ipo influence
+						}
+						boneTrack.setKeyframes(boneTrack.getTimes(), boneTrack.getTranslations(), rotations, boneTrack.getScales());
+					}
+				}
+			};
+
+			//ROTLIMIT constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_ROTLIMIT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_ROTLIMIT, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					Structure constraintStructure = constraint.getData();
+					this.validateConstraintType(constraintStructure);
+					BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint);
+					if(boneTrack != null) {
+						int flag = ((Number)constraintStructure.getFieldValue("flag")).intValue();
+						Quaternion[] rotations = boneTrack.getRotations();
+						int maxFrames = rotations.length;
+						for(int frame = 0; frame < maxFrames; ++frame) {
+							float[] angles = rotations[frame].toAngles(null);
+							float influence = constraint.getIpo().calculateValue(frame);
+							if((flag & LIMIT_XROT) != 0) {
+								float xmin = ((Number)constraintStructure.getFieldValue("xmin")).floatValue() * FastMath.DEG_TO_RAD;
+								float xmax = ((Number)constraintStructure.getFieldValue("xmax")).floatValue() * FastMath.DEG_TO_RAD;
+								float difference = 0.0f;
+								if(angles[0] < xmin) {
+									difference = (angles[0] - xmin) * influence;
+								} else if(angles[0] > xmax) {
+									difference = (angles[0] - xmax) * influence;
+								}
+								angles[0] -= difference;
+							}
+							if((flag & LIMIT_YROT) != 0) {
+								float ymin = ((Number)constraintStructure.getFieldValue("ymin")).floatValue() * FastMath.DEG_TO_RAD;
+								float ymax = ((Number)constraintStructure.getFieldValue("ymax")).floatValue() * FastMath.DEG_TO_RAD;
+								float difference = 0.0f;
+								if(angles[1] < ymin) {
+									difference = (angles[1] - ymin) * influence;
+								} else if(angles[1] > ymax) {
+									difference = (angles[1] - ymax) * influence;
+								}
+								angles[1] -= difference;
+							}
+							if((flag & LIMIT_ZROT) != 0) {
+								float zmin = ((Number)constraintStructure.getFieldValue("zmin")).floatValue() * FastMath.DEG_TO_RAD;
+								float zmax = ((Number)constraintStructure.getFieldValue("zmax")).floatValue() * FastMath.DEG_TO_RAD;
+								float difference = 0.0f;
+								if(angles[2] < zmin) {
+									difference = (angles[2] - zmin) * influence;
+								} else if(angles[2] > zmax) {
+									difference = (angles[2] - zmax) * influence;
+								}
+								angles[2] -= difference;
+							}
+							rotations[frame].fromAngles(angles);//TODO: consider constraint space !!!
+						}
+						boneTrack.setKeyframes(boneTrack.getTimes(), boneTrack.getTranslations(), rotations, boneTrack.getScales());
+					}
+				}
+			};
+
+			//SHRINKWRAP constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_SHRINKWRAP.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_SHRINKWRAP, dataRepository) {};
+
+			//SIZELIKE constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_SIZELIKE.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_SIZELIKE, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					Structure constraintData = constraint.getData();
+					this.validateConstraintType(constraintData);
+					Vector3f targetScale = this.getTargetLocation(constraint);
+					BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint);
+					if(boneTrack != null) {
+						int flag = ((Number)constraintData.getFieldValue("flag")).intValue();
+						Vector3f[] scales = boneTrack.getScales();
+						int maxFrames = scales.length;
+						for(int frame = 0; frame < maxFrames; ++frame) {
+							Vector3f offset = Vector3f.ZERO;
+							if((flag & LOCLIKE_OFFSET) != 0) {//we add the original scale to the copied scale
+								offset = scales[frame].clone();
+							}
+
+							if((flag & SIZELIKE_X) != 0) {
+								scales[frame].x = targetScale.x;
+							} else if((flag & SIZELIKE_Y) != 0) {
+								scales[frame].y = targetScale.y;
+							} else if((flag & SIZELIKE_Z) != 0) {
+								scales[frame].z = targetScale.z;
+							}
+							scales[frame].addLocal(offset);//TODO: ipo influence
+							//TODO: add or multiply???
+						}
+						boneTrack.setKeyframes(boneTrack.getTimes(), boneTrack.getTranslations(), boneTrack.getRotations(), scales);
+					}
+				}
+			};
+
+			//SIZELIMIT constraint
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_SIZELIMIT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_SIZELIMIT, dataRepository) {
+				@Override
+				public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+					Structure constraintStructure = constraint.getData();
+					this.validateConstraintType(constraintStructure);
+					BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint);
+					if(boneTrack != null) {
+						int flag = ((Number)constraintStructure.getFieldValue("flag")).intValue();
+						Vector3f[] scales = boneTrack.getScales();
+						int maxFrames = scales.length;
+						for(int frame = 0; frame < maxFrames; ++frame) {
+							float influence = constraint.getIpo().calculateValue(frame);
+							if((flag & LIMIT_XMIN) != 0) {
+								float xmin = ((Number)constraintStructure.getFieldValue("xmin")).floatValue();
+								if(scales[frame].x < xmin) {
+									scales[frame].x -= (scales[frame].x - xmin) * influence;
+								}
+							}
+							if((flag & LIMIT_XMAX) != 0) {
+								float xmax = ((Number)constraintStructure.getFieldValue("xmax")).floatValue();
+								if(scales[frame].x > xmax) {
+									scales[frame].x -= (scales[frame].x - xmax) * influence;
+								}
+							}
+							if((flag & LIMIT_YMIN) != 0) {
+								float ymin = ((Number)constraintStructure.getFieldValue("ymin")).floatValue();
+								if(scales[frame].y < ymin) {
+									scales[frame].y -= (scales[frame].y - ymin) * influence;
+								}
+							}
+							if((flag & LIMIT_YMAX) != 0) {
+								float ymax = ((Number)constraintStructure.getFieldValue("ymax")).floatValue();
+								if(scales[frame].y > ymax) {
+									scales[frame].y -= (scales[frame].y - ymax) * influence;
+								}
+							}
+							if((flag & LIMIT_ZMIN) != 0) {
+								float zmin = ((Number)constraintStructure.getFieldValue("zmin")).floatValue();
+								if(scales[frame].z < zmin) {
+									scales[frame].z -= (scales[frame].z - zmin) * influence;
+								}
+							}
+							if((flag & LIMIT_ZMAX) != 0) {
+								float zmax = ((Number)constraintStructure.getFieldValue("zmax")).floatValue();
+								if(scales[frame].z > zmax) {
+									scales[frame].z -= (scales[frame].z - zmax) * influence;
+								}
+							}//TODO: consider constraint space !!!
+						}
+						boneTrack.setKeyframes(boneTrack.getTimes(), boneTrack.getTranslations(), boneTrack.getRotations(), scales);
+					}
+				}
+			};
+
+			//STRETCHTO constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_STRETCHTO.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_STRETCHTO, dataRepository) {};
+
+			//TRANSFORM constraint (TODO: to implement)
+			influenceFunctions[ConstraintType.CONSTRAINT_TYPE_TRANSFORM.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_TRANSFORM, dataRepository) {};
+		}
+	}
+
+	/**
+	 * This method reads constraints for for the given structure. The constraints are loaded only once for object/bone.
+	 * @param ownerOMA
+	 *        the owner's old memory address
+	 * @param objectStructure
+	 *        the structure we read constraint's for
+	 * @param dataRepository
+	 *        the data repository
+	 * @throws BlenderFileException
+	 */
+	public void loadConstraints(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
+		// reading influence ipos for the constraints
+		IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class);
+		Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();
+		Pointer pActions = (Pointer)objectStructure.getFieldValue("action");
+		if(!pActions.isNull()) {
+			List<Structure> actions = pActions.fetchData(dataRepository.getInputStream());
+			for(Structure action : actions) {
+				Structure chanbase = (Structure)action.getFieldValue("chanbase");
+				List<Structure> actionChannels = chanbase.evaluateListBase(dataRepository);
+				for(Structure actionChannel : actionChannels) {
+					Map<String, Ipo> ipos = new HashMap<String, Ipo>();
+					Structure constChannels = (Structure)actionChannel.getFieldValue("constraintChannels");
+					List<Structure> constraintChannels = constChannels.evaluateListBase(dataRepository);
+					for(Structure constraintChannel : constraintChannels) {
+						Pointer pIpo = (Pointer)constraintChannel.getFieldValue("ipo");
+						if(!pIpo.isNull()) {
+							String constraintName = constraintChannel.getFieldValue("name").toString();
+							Ipo ipo = ipoHelper.createIpo(pIpo.fetchData(dataRepository.getInputStream()).get(0), dataRepository);
+							ipos.put(constraintName, ipo);
+						}
+					}
+					String actionName = actionChannel.getFieldValue("name").toString();
+					constraintsIpos.put(actionName, ipos);
+				}
+			}
+		}
+
+		//loading constraints connected with the object's bones
+		List<Constraint> constraintsList = new ArrayList<Constraint>();
+		Pointer pPose = (Pointer)objectStructure.getFieldValue("pose");//TODO: what if the object has two armatures ????
+		if(!pPose.isNull()) {
+			//getting pose channels
+			List<Structure> poseChannels = ((Structure)pPose.fetchData(dataRepository.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(dataRepository);
+			for(Structure poseChannel : poseChannels) {
+				Long boneOMA = Long.valueOf(((Pointer)poseChannel.getFieldValue("bone")).getOldMemoryAddress());
+				//the name is read directly from structure because bone might not yet be loaded
+				String name = dataRepository.getFileBlock(boneOMA).getStructure(dataRepository).getFieldValue("name").toString();
+				List<Structure> constraints = ((Structure)poseChannel.getFieldValue("constraints")).evaluateListBase(dataRepository);
+				for(Structure constraint : constraints) {
+					int type = ((Number)constraint.getFieldValue("type")).intValue();
+					String constraintName = constraint.getFieldValue("name").toString();
+					Ipo ipo = constraintsIpos.get(name).get(constraintName);
+					if(ipo == null) {
+						float enforce = ((Number)constraint.getFieldValue("enforce")).floatValue();
+						ipo = ipoHelper.createIpo(enforce);
+					}
+					Space ownerSpace = Space.valueOf(((Number)constraint.getFieldValue("ownspace")).byteValue());
+					Space targetSpace = Space.valueOf(((Number)constraint.getFieldValue("tarspace")).byteValue());
+					Constraint c = new Constraint(constraint, influenceFunctions[type], boneOMA, ownerSpace, targetSpace, ipo, dataRepository);
+					constraintsList.add(c);
+				}
+			}
+		}
+		/* TODO: reading constraints for objects (implement when object's animation will be available)
+		List<Structure> constraintChannels = ((Structure)objectStructure.getFieldValue("constraintChannels")).evaluateListBase(dataRepository);
+		for(Structure constraintChannel : constraintChannels) {
+			System.out.println(constraintChannel);
+		}
+
+		//loading constraints connected with the object itself (TODO: test this)
+		if(!this.constraints.containsKey(objectStructure.getOldMemoryAddress())) {
+			List<Structure> constraints = ((Structure)objectStructure.getFieldValue("constraints")).evaluateListBase(dataRepository);
+			Constraint[] result = new Constraint[constraints.size()];
+			int i = 0;
+			for(Structure constraint : constraints) {
+				int type = ((Number)constraint.getFieldValue("type")).intValue();
+				String name = constraint.getFieldValue("name").toString();
+				result[i++] = new Constraint(constraint, influenceFunctions[type], null, dataRepository);//TODO: influence ipos for object animation
+			}
+			this.constraints.put(objectStructure.getOldMemoryAddress(), result);
+		}
+		*/
+		if(constraintsList.size() > 0) {
+			this.constraints.put(objectStructure.getOldMemoryAddress(), constraintsList.toArray(new Constraint[constraintsList.size()]));
+		}
+	}
+
+	/**
+	 * This method returns a list of constraints of the feature's constraints. The order of constraints is important.
+	 * @param ownerOMA
+	 *        the owner's old memory address
+	 * @return a table of constraints for the feature specified by old memory address
+	 */
+	public Constraint[] getConstraints(Long ownerOMA) {
+		return constraints.get(ownerOMA);
+	}
+
+	@Override
+	public void clearState() {
+		constraints.clear();
+	}
+
+	/**
+	 * The purpose of this class is to imitate bone's movement when calculating inverse kinematics.
+	 * @author Marcin Roguski
+	 */
+	private static class CalculationBone extends Node {
+		/** The name of the bone. Only to be used in toString method. */
+		private String		boneName;
+		/** The bone's tracks. Will be altered at the end of calculation process. */
+		private BoneTrack	track;
+		/** The starting position of the bone. */
+		private Vector3f	startTranslation;
+		/** The starting rotation of the bone. */
+		private Quaternion	startRotation;
+		/** The starting scale of the bone. */
+		private Vector3f	startScale;
+		
+		private Vector3f[] translations;
+		private Quaternion[] rotations;
+		private Vector3f[] scales;
+
+		/**
+		 * Constructor. Stores the track, starting transformation and sets the transformation to the starting positions.
+		 * @param bone
+		 *        the bone this class will imitate
+		 * @param track
+		 *        the bone's tracks
+		 */
+		public CalculationBone(Bone bone, BoneTrack track) {
+			this.boneName = bone.getName();
+			this.track = track;
+			this.startRotation = bone.getModelSpaceRotation().clone();
+			this.startTranslation = bone.getModelSpacePosition().clone();
+			this.startScale = bone.getModelSpaceScale().clone();
+			this.translations = track.getTranslations();
+			this.rotations = track.getRotations();
+			this.scales = track.getScales();
+			this.reset();
+		}
+
+		/**
+		 * This method returns the end point of the bone. If the bone has parent it is calculated from the start point
+		 * of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered
+		 * to be 1 point up along Y axis (scale is applied if set to != 1.0);
+		 * @return the end point of this bone
+		 */
+		//TODO: set to Z axis if user defined it this way
+		public Vector3f getEndPoint() {
+			if(this.getParent() == null) {
+				return new Vector3f(0, this.getLocalScale().y, 0);
+			} else {
+				Node parent = this.getParent();
+				return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale());
+			}
+		}
+
+		/**
+		 * This method resets the calculation bone to the starting position.
+		 */
+		public void reset() {
+			this.setLocalTranslation(startTranslation);
+			this.setLocalRotation(startRotation);
+			this.setLocalScale(startScale);
+		}
+
+		@Override
+		public int attachChild(Spatial child) {
+			if(this.getChildren() != null && this.getChildren().size() > 1) {
+				throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!");
+			}
+			return super.attachChild(child);
+		}
+		
+		public Spatial rotate(Quaternion rot, int frame) {
+			Spatial spatial = super.rotate(rot);
+			this.updateWorldTransforms();
+			if(this.getChildren()!=null && this.getChildren().size()>0) {
+				CalculationBone child = (CalculationBone)this.getChild(0);
+				child.updateWorldTransforms();
+			}
+			rotations[frame].set(this.getLocalRotation());
+			translations[frame].set(this.getLocalTranslation());
+			if(scales!=null) {
+				scales[frame].set(this.getLocalScale());
+			}
+			return spatial;
+		}
+		
+		public void applyCalculatedTracks() {
+			track.setKeyframes(track.getTimes(), translations, rotations);//TODO:scales
+		}
+		
+		@Override
+		public String toString() {
+			return boneName+ ": " + this.getLocalRotation() + " " + this.getLocalTranslation();
+		}
+	}
+}

+ 590 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java

@@ -0,0 +1,590 @@
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+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.Logger;
+
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Spline;
+import com.jme3.math.Spline.SplineType;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.structures.BezierCurve;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+import com.jme3.scene.shape.Curve;
+import com.jme3.scene.shape.Surface;
+import com.jme3.util.BufferUtils;
+
+/**
+ * A class that is used in mesh calculations.
+ * @author Marcin Roguski
+ */
+public class CurvesHelper extends AbstractBlenderHelper {
+	private static final Logger			LOGGER		= Logger.getLogger(CurvesHelper.class.getName());
+	
+	/** This variable indicates if the Y asxis is the UP axis or not. */
+	protected boolean				fixUpAxis;
+	/** Minimum basis U function degree for NURBS curves and surfaces. */
+	protected int					minimumBasisUFunctionDegree = 4;
+	/** Minimum basis V function degree for NURBS curves and surfaces. */
+	protected int					minimumBasisVFunctionDegree = 4;
+	
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public CurvesHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+	
+	/**
+	 * This method sets the Y is UP axis. By default the UP axis is Z (just like in blender).
+	 * @param fixUpAxis
+	 *        a variable that indicates if the Y asxis is the UP axis or not
+	 */
+	public void setyIsUpAxis(boolean fixUpAxis) {
+		this.fixUpAxis = fixUpAxis;
+	}
+
+	/**
+	 * This method converts given curve structure into a list of geometries representing the curve. The list is used here because on object
+	 * can have several separate curves.
+	 * @param curveStructure
+	 *            the curve structure
+	 * @param dataRepository
+	 *            the data repository
+	 * @return a list of geometries repreenting a single curve object
+	 * @throws BlenderFileException
+	 */
+	public List<Geometry> toCurve(Structure curveStructure, DataRepository dataRepository) throws BlenderFileException {
+		String name = curveStructure.getName();
+		int flag = ((Number)curveStructure.getFieldValue("flag")).intValue();
+		boolean is3D = (flag & 0x01) != 0;
+		boolean isFront = (flag & 0x02) != 0 && !is3D;
+		boolean isBack = (flag & 0x04) != 0 && !is3D;
+		if(isFront) {
+			LOGGER.warning("No front face in curve implemented yet!");//TODO: implement front face
+		}
+		if(isBack) {
+			LOGGER.warning("No back face in curve implemented yet!");//TODO: implement back face
+		}
+		
+		//reading nurbs (and sorting them by material)
+		List<Structure> nurbStructures = ((Structure)curveStructure.getFieldValue("nurb")).evaluateListBase(dataRepository);
+		Map<Number, List<Structure>> nurbs = new HashMap<Number, List<Structure>>();
+		for(Structure nurb : nurbStructures) {
+			Number matNumber = (Number) nurb.getFieldValue("mat_nr");
+			List<Structure> nurbList = nurbs.get(matNumber);
+			if(nurbList==null) {
+				nurbList = new ArrayList<Structure>();
+				nurbs.put(matNumber, nurbList);
+			}
+			nurbList.add(nurb);
+		}
+		
+		//getting materials
+		MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+		Material[] materials = materialHelper.getMaterials(curveStructure, dataRepository);
+		if(materials==null) {
+			materials = new Material[] { dataRepository.getDefaultMaterial().clone() };
+		}
+		for(Material material : materials) {
+			material.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
+		}
+		
+		//getting or creating bevel object
+		List<Geometry> bevelObject = null;
+		Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj");
+		if(!pBevelObject.isNull()) {
+			Pointer pBevelStructure = (Pointer) pBevelObject.fetchData(dataRepository.getInputStream()).get(0).getFieldValue("data");
+			Structure bevelStructure = pBevelStructure.fetchData(dataRepository.getInputStream()).get(0);
+			bevelObject = this.toCurve(bevelStructure, dataRepository);
+		} else {
+			int bevResol = ((Number)curveStructure.getFieldValue("bevresol")).intValue();
+			float extrude = ((Number)curveStructure.getFieldValue("ext1")).floatValue();
+			float bevelDepth = ((Number)curveStructure.getFieldValue("ext2")).floatValue();
+			if(bevelDepth>0.0f) {
+				float handlerLength = bevelDepth/2.0f;
+				
+				List<Vector3f> conrtolPoints = new ArrayList<Vector3f>(extrude>0.0f ? 19 : 13);
+				conrtolPoints.add(new Vector3f(-bevelDepth,extrude,0));
+				conrtolPoints.add(new Vector3f(-bevelDepth,handlerLength+extrude,0));
+				
+				conrtolPoints.add(new Vector3f(-handlerLength,bevelDepth+extrude,0));
+				conrtolPoints.add(new Vector3f(0,bevelDepth+extrude,0));
+				conrtolPoints.add(new Vector3f(handlerLength,bevelDepth+extrude,0));
+				
+				conrtolPoints.add(new Vector3f(bevelDepth,extrude + handlerLength,0));
+				conrtolPoints.add(new Vector3f(bevelDepth,extrude,0));
+				conrtolPoints.add(new Vector3f(bevelDepth,extrude - handlerLength,0));
+				
+				if(extrude>0.0f) {
+					conrtolPoints.add(new Vector3f(bevelDepth,-extrude + handlerLength,0));
+					conrtolPoints.add(new Vector3f(bevelDepth,-extrude,0));
+					conrtolPoints.add(new Vector3f(bevelDepth,-extrude-handlerLength,0));
+				}
+				
+				conrtolPoints.add(new Vector3f(handlerLength,-bevelDepth-extrude,0));
+				conrtolPoints.add(new Vector3f(0,-bevelDepth-extrude,0));
+				conrtolPoints.add(new Vector3f(-handlerLength,-bevelDepth-extrude,0));
+				
+				conrtolPoints.add(new Vector3f(-bevelDepth,-handlerLength - extrude,0));
+				conrtolPoints.add(new Vector3f(-bevelDepth,-extrude,0));
+				
+				if(extrude>0.0f) {
+					conrtolPoints.add(new Vector3f(-bevelDepth,handlerLength - extrude,0));
+					
+					conrtolPoints.add(new Vector3f(-bevelDepth,-handlerLength + extrude,0));
+					conrtolPoints.add(new Vector3f(-bevelDepth,extrude,0));
+				}
+				
+				Spline bevelSpline = new Spline(SplineType.Bezier, conrtolPoints, 0, false);
+				Curve bevelCurve = new Curve(bevelSpline, bevResol);
+				bevelObject = new ArrayList<Geometry>(1);
+				bevelObject.add(new Geometry("", bevelCurve));
+			} else if(extrude>0.0f) {
+				Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[] {
+						new Vector3f(0, extrude, 0), new Vector3f(0, -extrude, 0)
+				}, 1, false);
+				Curve bevelCurve = new Curve(bevelSpline, bevResol);
+				bevelObject = new ArrayList<Geometry>(1);
+				bevelObject.add(new Geometry("", bevelCurve));
+			}
+		}
+		
+		//getting taper object
+		Curve taperObject = null;
+		Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj");
+		if(bevelObject!=null && !pTaperObject.isNull()) {
+			Pointer pTaperStructure = (Pointer) pTaperObject.fetchData(dataRepository.getInputStream()).get(0).getFieldValue("data");
+			Structure taperStructure = pTaperStructure.fetchData(dataRepository.getInputStream()).get(0);
+			taperObject = this.loadTaperObject(taperStructure, dataRepository);
+		}
+		
+		Vector3f loc = this.getLoc(curveStructure);
+		//creating the result curves
+		List<Geometry> result = new ArrayList<Geometry>(nurbs.size());
+		for(Entry<Number, List<Structure>> nurbEntry : nurbs.entrySet()) {
+			for(Structure nurb : nurbEntry.getValue()) {
+				int type = ((Number)nurb.getFieldValue("type")).intValue();
+				List<Geometry> nurbGeoms = null;
+				if((type & 0x01)!=0) {//Bezier curve
+					nurbGeoms = this.loadBezierCurve(loc, nurb, bevelObject, taperObject, dataRepository);
+				} else if((type & 0x04)!=0) {//NURBS
+					nurbGeoms = this.loadNurb(loc, nurb, bevelObject, taperObject, dataRepository);
+				}
+				if(nurbGeoms!=null) {//setting the name and assigning materials
+					for(Geometry nurbGeom : nurbGeoms) {
+						nurbGeom.setMaterial(materials[nurbEntry.getKey().intValue()]);
+						nurbGeom.setName(name);
+						result.add(nurbGeom);
+					}
+				}
+			}
+		}
+		return result;
+	}
+	
+	/**
+	 * This method loads the bezier curve.
+	 * @param loc
+	 *            the translation of the curve
+	 * @param nurb
+	 *            the nurb structure
+	 * @param bevelObject
+	 *            the bevel object
+	 * @param taperObject
+	 *            the taper object
+	 * @param dataRepository
+	 *            the data repository
+	 * @return a list of geometries representing the curves
+	 * @throws BlenderFileException
+	 *             an exception is thrown when there are problems with the blender file
+	 */
+	protected List<Geometry> loadBezierCurve(Vector3f loc, Structure nurb, List<Geometry> bevelObject, Curve taperObject, 
+											 DataRepository dataRepository) throws BlenderFileException {
+		Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt");
+		List<Geometry> result = new ArrayList<Geometry>();
+		if(!pBezierTriple.isNull()) {
+			boolean smooth = (((Number)nurb.getFlatFieldValue("flag")).intValue() & 0x01) !=0;
+			int resolution = ((Number)nurb.getFieldValue("resolu")).intValue();
+			boolean cyclic = (((Number)nurb.getFieldValue("flagu")).intValue() & 0x01) != 0;
+			
+			//creating the curve object
+			BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(dataRepository.getInputStream()), 3);
+			List<Vector3f> controlPoints = bezierCurve.getControlPoints();
+			if(cyclic) {
+				//copy the first three points at the end
+				for(int i=0;i<3;++i) {
+					controlPoints.add(controlPoints.get(i));
+				}
+			}
+			//removing the first and last handles
+			controlPoints.remove(0);
+			controlPoints.remove(controlPoints.size()-1);
+
+			//creating curve
+			Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false);
+			Curve curve = new Curve(spline, resolution);
+			if(bevelObject==null) {//creating a normal curve
+				Geometry curveGeometry = new Geometry(null, curve);
+				result.add(curveGeometry);
+				//TODO: use front and back flags; surface excluding algorithm for bezier circles should be added
+			} else {//creating curve with bevel and taper shape
+				result = this.applyBevelAndTaper(curve, bevelObject, taperObject, smooth, dataRepository);
+			}
+		}
+		return result;
+	}
+	
+	/**
+	 * This method loads the NURBS curve or surface.
+	 * @param loc
+	 *            object's location
+	 * @param nurb
+	 *            the NURBS data structure
+	 * @param bevelObject
+	 *            the bevel object to be applied
+	 * @param taperObject
+	 *            the taper object to be applied
+	 * @param dataRepository
+	 *            the data repository
+	 * @return a list of geometries that represents the loaded NURBS curve or surface
+	 * @throws BlenderFileException
+	 *             an exception is throw when problems with blender loaded data occurs
+	 */
+	@SuppressWarnings("unchecked")
+	protected List<Geometry> loadNurb(Vector3f loc, Structure nurb, List<Geometry> bevelObject, Curve taperObject, 
+			 						  DataRepository dataRepository) throws BlenderFileException {
+		//loading the knots
+		List<Float>[] knots = new List[2];
+		Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") };
+		for(int i=0;i<knots.length; ++i) {
+			if(!pKnots[i].isNull()) {
+				FileBlockHeader fileBlockHeader = dataRepository.getFileBlock(pKnots[i].getOldMemoryAddress());
+				BlenderInputStream blenderInputStream = dataRepository.getInputStream();
+				blenderInputStream.setPosition(fileBlockHeader.getBlockPosition());
+				int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4;
+				knots[i] = new ArrayList<Float>(knotsAmount);
+				for(int j=0;j<knotsAmount;++j) {
+					knots[i].add(Float.valueOf(blenderInputStream.readFloat()));
+				}
+			}
+		}
+		
+		//loading the flags and orders (basis functions degrees)
+		int flagU = ((Number)nurb.getFieldValue("flagu")).intValue();
+		int flagV = ((Number)nurb.getFieldValue("flagv")).intValue();
+		int orderU = ((Number)nurb.getFieldValue("orderu")).intValue();
+		int orderV = ((Number)nurb.getFieldValue("orderv")).intValue();
+		
+		//loading control points and their weights
+		int pntsU = ((Number)nurb.getFieldValue("pntsu")).intValue();
+		int pntsV = ((Number)nurb.getFieldValue("pntsv")).intValue();
+		List<Structure> bPoints = ((Pointer)nurb.getFieldValue("bp")).fetchData(dataRepository.getInputStream());
+		List<List<Vector4f>> controlPoints = new ArrayList<List<Vector4f>>(pntsV);
+		for(int i=0;i<pntsV;++i) {
+			List<Vector4f> uControlPoints = new ArrayList<Vector4f>(pntsU);
+			for(int j=0;j<pntsU;++j) {
+				DynamicArray<Float> vec = (DynamicArray<Float>)bPoints.get(j + i*pntsU).getFieldValue("vec");
+				if(fixUpAxis) {
+					uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue()));
+				} else {
+					uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue()));
+				}
+			}
+			if((flagU & 0x01) != 0) {
+				for(int k=0;k<orderU - 1;++k) {
+					uControlPoints.add(uControlPoints.get(k));
+				}
+			}
+			controlPoints.add(uControlPoints);
+		}
+		if((flagV & 0x01) != 0) {
+			for(int k=0;k<orderV - 1;++k) {
+				controlPoints.add(controlPoints.get(k));
+			}
+		}
+		
+		int resolu = ((Number)nurb.getFieldValue("resolu")).intValue() + 1;
+		List<Geometry> result;
+		if(knots[1]==null) {//creating the curve
+			Spline nurbSpline = new Spline(controlPoints.get(0), knots[0]);
+			Curve nurbCurve = new Curve(nurbSpline, resolu);
+			if(bevelObject!=null) {
+				result = this.applyBevelAndTaper(nurbCurve, bevelObject, taperObject, true, dataRepository);//TODO: smooth
+			} else {
+				result = new ArrayList<Geometry>(1);
+				Geometry nurbGeometry = new Geometry("", nurbCurve);
+				result.add(nurbGeometry);
+			}
+		} else {//creating the nurb surface
+			int resolv = ((Number)nurb.getFieldValue("resolv")).intValue() + 1;
+			Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, resolu, resolv, orderU, orderV);
+			Geometry nurbGeometry = new Geometry("", nurbSurface);
+			result = new ArrayList<Geometry>(1);
+			result.add(nurbGeometry);
+		}
+		return result;
+	}
+	
+	/**
+	 * This method returns the taper scale that should be applied to the object.
+	 * @param taperPoints
+	 *            the taper points
+	 * @param taperLength
+	 *            the taper curve length
+	 * @param percent
+	 *            the percent of way along the whole taper curve
+	 * @param store
+	 *            the vector where the result will be stored
+	 */
+	protected float getTaperScale(float[] taperPoints, float taperLength, float percent) {
+		float length = taperLength * percent;
+		float currentLength = 0;
+		Vector3f p = new Vector3f();
+		int i;
+		for(i=0;i<taperPoints.length-6 && currentLength < length; i += 3) {
+			p.set(taperPoints[i], taperPoints[i+1], taperPoints[i+2]);
+			p.subtractLocal(taperPoints[i+3], taperPoints[i+4], taperPoints[i+5]);
+			currentLength += p.length();
+		}
+		currentLength -= p.length();
+		float leftLength = length - currentLength;
+		float percentOnSegment = p.length()==0 ? 0 : leftLength / p.length();
+		Vector3f store = FastMath.interpolateLinear(percentOnSegment, 
+													new Vector3f(taperPoints[i], taperPoints[i+1], taperPoints[i+2]), 
+													new Vector3f(taperPoints[i+3], taperPoints[i+4], taperPoints[i+5]));
+		return store.y;
+	}
+	
+	/**
+	 * This method applies bevel and taper objects to the curve.
+	 * @param curve
+	 *            the curve we apply the objects to
+	 * @param bevelObject
+	 *            the bevel object
+	 * @param taperObject
+	 *            the taper object
+	 * @param smooth
+	 * 			  the smooth flag
+	 * @param dataRepository
+	 *            the data repository
+	 * @return a list of geometries representing the beveled and/or tapered curve
+	 */
+	protected List<Geometry> applyBevelAndTaper(Curve curve, List<Geometry> bevelObject, Curve taperObject, 
+												boolean smooth, DataRepository dataRepository) {
+		float[] curvePoints = BufferUtils.getFloatArray(curve.getFloatBuffer(Type.Position));
+		MeshHelper meshHelper = dataRepository.getHelper(MeshHelper.class);
+		float curveLength = curve.getLength();
+		//TODO: use the smooth var
+		
+		//taper data
+		float[] taperPoints = null;
+		float taperLength = 0;
+		if(taperObject!=null) {
+			taperPoints = BufferUtils.getFloatArray(taperObject.getFloatBuffer(Type.Position));
+			taperLength = taperObject.getLength();
+		}
+		
+		//several objects can be allocated only once
+		Vector3f p = new Vector3f();
+		Vector3f z = new Vector3f(0,0,1);
+		Vector3f negativeY = new Vector3f(0, -1, 0);
+		Matrix4f m = new Matrix4f();
+		float lengthAlongCurve = 0, taperScale = 1.0f;
+		Quaternion planeRotation = new Quaternion();
+		Quaternion zRotation = new Quaternion();
+		float[] temp = new float[] {0,0,0,1};
+		Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>();//normalMap merges normals of faces that will be rendered smooth
+		
+		FloatBuffer[] vertexBuffers = new FloatBuffer[bevelObject.size()];
+		FloatBuffer[] normalBuffers = new FloatBuffer[bevelObject.size()];
+		IntBuffer[] indexBuffers = new IntBuffer[bevelObject.size()];
+		for(int geomIndex = 0;geomIndex<bevelObject.size();++geomIndex) {
+			Mesh mesh = bevelObject.get(geomIndex).getMesh();
+			FloatBuffer positions = mesh.getFloatBuffer(Type.Position);
+			float[] vertices = BufferUtils.getFloatArray(positions);
+			
+			for(int i=0;i<curvePoints.length;i+=3) {
+				p.set(curvePoints[i], curvePoints[i+1], curvePoints[i+2]);
+				Vector3f v;
+				if(i==0) {
+					v = new Vector3f(curvePoints[3] - p.x, curvePoints[4] - p.y, curvePoints[5] - p.z);
+				} else if(i+3>=curvePoints.length) {
+					v = new Vector3f(p.x - curvePoints[i-3], p.y - curvePoints[i-2], p.z - curvePoints[i-1]);
+					lengthAlongCurve += v.length();
+				} else {
+					v = new Vector3f(curvePoints[i+3] - curvePoints[i-3],
+							 curvePoints[i+4] - curvePoints[i-2],
+							 curvePoints[i+5] - curvePoints[i-1]);
+					lengthAlongCurve += new Vector3f(curvePoints[i+3] - p.x, curvePoints[i+4] - p.y, curvePoints[i+5] - p.z).length();
+				}
+				v.normalizeLocal();
+				
+				float angle = FastMath.acos(v.dot(z));
+				v.crossLocal(z).normalizeLocal();//v is the rotation axis now
+				planeRotation.fromAngleAxis(angle, v);
+				
+				Vector3f zAxisRotationVector = negativeY.cross(v).normalizeLocal();
+				float zAxisRotationAngle = FastMath.acos(negativeY.dot(v));
+				zRotation.fromAngleAxis(zAxisRotationAngle, zAxisRotationVector);
+				
+				//point transformation matrix
+				if(taperPoints!=null) {
+					taperScale = this.getTaperScale(taperPoints, taperLength, lengthAlongCurve / curveLength);
+				}
+				m.set(Matrix4f.IDENTITY);
+				m.setRotationQuaternion(planeRotation.multLocal(zRotation));
+				m.setTranslation(p);
+				
+				//these vertices need to be thrown on XY plane
+				//and moved to the origin of [p1.x, p1.y] on the plane
+				Vector3f[] verts = new Vector3f[vertices.length/3];
+				for(int j=0;j<verts.length;++j) {
+					 temp[0] = vertices[j*3] * taperScale;
+					 temp[1] = vertices[j*3+1] * taperScale;
+					 temp[2] = 0;
+					 m.mult(temp);//the result is stored in the array
+					 if(fixUpAxis) {
+						 verts[j] = new Vector3f(temp[0], temp[1], temp[2]);
+					 } else {
+						 verts[j] = new Vector3f(temp[0], temp[2], -temp[1]);
+					 }
+				}
+				if(vertexBuffers[geomIndex]==null) {
+					vertexBuffers[geomIndex] = BufferUtils.createFloatBuffer(verts.length * curvePoints.length);
+				}
+				FloatBuffer buffer = BufferUtils.createFloatBuffer(verts);
+				vertexBuffers[geomIndex].put(buffer);
+				
+				//adding indexes
+				IntBuffer indexBuffer = indexBuffers[geomIndex];
+				if(indexBuffer==null) {
+					//the amount of faces in the final mesh is the amount of edges in the bevel curve
+					//(which is less by 1 than its number of vertices)
+					//multiplied by 2 (because each edge has two faces assigned on both sides)
+					//and multiplied by the amount of bevel curve repeats which is equal to the amount of vertices on the target curve
+					//finally we need to subtract the bevel edges amount 2 times because the border edges have only one face attached
+					//and at last multiply everything by 3 because each face needs 3 indexes to be described
+					int bevelCurveEdgesAmount = verts.length-1;
+					indexBuffer = BufferUtils.createIntBuffer(((bevelCurveEdgesAmount << 1) * curvePoints.length - bevelCurveEdgesAmount << 1) * 3);
+					indexBuffers[geomIndex] = indexBuffer;
+				}
+				int pointOffset = i/3 * verts.length;
+				if(i+3<curvePoints.length) {
+					for(int index=0;index<verts.length-1;++index) {
+						indexBuffer.put(index + pointOffset);
+						indexBuffer.put(index + pointOffset + 1);
+						indexBuffer.put(verts.length + index + pointOffset);
+						indexBuffer.put(verts.length + index + pointOffset);
+						indexBuffer.put(index + pointOffset + 1);
+						indexBuffer.put(verts.length + index + pointOffset + 1);
+					}
+				}
+			}
+		}
+		
+		//calculating the normals
+		for(int geomIndex = 0;geomIndex<bevelObject.size();++geomIndex) {
+			Vector3f[] allVerts = BufferUtils.getVector3Array(vertexBuffers[geomIndex]);
+			int[] allIndices = BufferUtils.getIntArray(indexBuffers[geomIndex]);
+			for(int i=0;i<allIndices.length-3;i+=3) {
+				Vector3f n = FastMath.computeNormal(allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]);
+				meshHelper.addNormal(n, normalMap, smooth, allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]);
+			}
+			if(normalBuffers[geomIndex]==null) {
+				normalBuffers[geomIndex] = BufferUtils.createFloatBuffer(allVerts.length * 3);
+			}
+			for(Vector3f v : allVerts) {
+				Vector3f n = normalMap.get(v);
+				normalBuffers[geomIndex].put(n.x);
+				normalBuffers[geomIndex].put(n.y);
+				normalBuffers[geomIndex].put(n.z);
+			}
+		}
+
+		List<Geometry> result = new ArrayList<Geometry>(vertexBuffers.length);
+		for(int i=0;i<vertexBuffers.length;++i) {
+			Mesh mesh = new Mesh();
+			mesh.setBuffer(Type.Position, 3, vertexBuffers[i]);
+			mesh.setBuffer(Type.Index, 3, indexBuffers[i]);
+			mesh.setBuffer(Type.Normal, 3, normalBuffers[i]);
+			Geometry g = new Geometry("g" + i, mesh);
+			g.updateModelBound();
+			result.add(g);
+		}
+		
+		return result;
+	}
+	
+	/**
+	 * This method loads the taper object.
+	 * @param taperStructure
+	 *            the taper structure
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the taper object
+	 * @throws BlenderFileException
+	 */
+	protected Curve loadTaperObject(Structure taperStructure, DataRepository dataRepository) throws BlenderFileException {
+		//reading nurbs
+		List<Structure> nurbStructures = ((Structure)taperStructure.getFieldValue("nurb")).evaluateListBase(dataRepository);
+		for(Structure nurb : nurbStructures) {
+			Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt");
+			if(!pBezierTriple.isNull()) {
+				//creating the curve object
+				BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(dataRepository.getInputStream()), 3);
+				List<Vector3f> controlPoints = bezierCurve.getControlPoints();
+				//removing the first and last handles
+				controlPoints.remove(0);
+				controlPoints.remove(controlPoints.size()-1);
+				
+				//return the first taper curve that has more than 3 control points
+				if(controlPoints.size()>3) {
+					Spline spline =  new Spline(SplineType.Bezier, controlPoints, 0, false);
+					int resolution = ((Number)taperStructure.getFieldValue("resolu")).intValue();
+					return new Curve(spline, resolution);
+				}
+			}
+		}
+		return null;
+	}
+	
+	/**
+	 * This method returns the translation of the curve. The UP axis is taken into account here.
+	 * @param curveStructure
+	 *            the curve structure
+	 * @return curve translation
+	 */
+	@SuppressWarnings("unchecked")
+	protected Vector3f getLoc(Structure curveStructure) {
+		DynamicArray<Number> locArray = (DynamicArray<Number>)curveStructure.getFieldValue("loc");
+		if(fixUpAxis) {
+			return new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), -locArray.get(2).floatValue());
+		} else {
+			return new Vector3f(locArray.get(0).floatValue(), locArray.get(2).floatValue(), locArray.get(1).floatValue());
+		}
+	}
+}

+ 114 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/IpoHelper.java

@@ -0,0 +1,114 @@
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.util.List;
+
+import com.jme3.animation.BoneTrack;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.structures.BezierCurve;
+import com.jme3.scene.plugins.blender.structures.Ipo;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+/**
+ * This class helps to compute values from interpolation curves for features like animation or constraint influence. The
+ * curves are 3rd degree bezier curves.
+ * @author Marcin Roguski
+ */
+public class IpoHelper extends AbstractBlenderHelper {
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public IpoHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	/**
+	 * This method creates an ipo object used for interpolation calculations.
+	 * @param ipoStructure
+	 *        the structure with ipo definition
+	 * @param dataRepository
+	 *        the data repository
+	 * @return the ipo object
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blender file is somehow corrupted
+	 */
+	public Ipo createIpo(Structure ipoStructure, DataRepository dataRepository) throws BlenderFileException {
+		Structure curvebase = (Structure)ipoStructure.getFieldValue("curve");
+
+		//preparing bezier curves
+		Ipo result = null;
+		List<Structure> curves = curvebase.evaluateListBase(dataRepository);//IpoCurve
+		if(curves.size() > 0) {
+			BezierCurve[] bezierCurves = new BezierCurve[curves.size()];
+			int frame = 0;
+			for(Structure curve : curves) {
+				Pointer pBezTriple = (Pointer)curve.getFieldValue("bezt");
+				List<Structure> bezTriples = pBezTriple.fetchData(dataRepository.getInputStream());
+				int type = ((Number)curve.getFieldValue("adrcode")).intValue();
+				bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2);
+			}
+			curves.clear();
+			result = new Ipo(bezierCurves);
+			dataRepository.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result);
+		}
+		return result;
+	}
+
+	/**
+	 * This method creates an ipo with only a single value. No track type is specified so do not use it for calculating
+	 * tracks.
+	 * @param constValue
+	 *        the value of this ipo
+	 * @return constant ipo
+	 */
+	public Ipo createIpo(float constValue) {
+		return new ConstIpo(constValue);
+	}
+
+	
+
+	/**
+	 * Ipo constant curve. This is a curve with only one value and no specified type. This type of ipo cannot be used to
+	 * calculate tracks. It should only be used to calculate single value for a given frame.
+	 * @author Marcin Roguski
+	 */
+	private class ConstIpo extends Ipo {
+		/** The constant value of this ipo. */
+		private float	constValue;
+
+		/**
+		 * Constructor. Stores the constant value of this ipo.
+		 * @param constValue
+		 *        the constant value of this ipo
+		 */
+		public ConstIpo(float constValue) {
+			super(null);
+			this.constValue = constValue;
+		}
+
+		@Override
+		public float calculateValue(int frame) {
+			return constValue;
+		}
+
+		@Override
+		public float calculateValue(int frame, int curveIndex) {
+			return constValue;
+		}
+
+		@Override
+		public int getCurvesAmount() {
+			return 0;
+		}
+		
+		@Override
+		public BoneTrack calculateTrack(int boneIndex, int startFrame, int stopFrame, int fps) {
+			throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
+		}
+	}
+}

+ 99 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/LightHelper.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.PointLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;
+
+/**
+ * A class that is used in light calculations.
+ * @author Marcin Roguski
+ */
+public class LightHelper extends AbstractBlenderHelper {
+	private static final Logger			LOGGER		= Logger.getLogger(LightHelper.class.getName());
+	
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public LightHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	public Light toLight(Structure structure, DataRepository dataRepository) throws BlenderFileException {
+		Light result = (Light)dataRepository.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+		if(result != null) {
+			return result;
+		}
+		int type = ((Number)structure.getFieldValue("type")).intValue();
+		switch(type) {
+			case 0://Lamp
+				result = new PointLight();
+				float distance = ((Number)structure.getFieldValue("dist")).floatValue();
+				((PointLight)result).setRadius(distance);
+				break;
+			case 1://Sun
+				LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine.");
+				break;
+			case 2://Spot
+				LOGGER.log(Level.WARNING, "'Spot' lamp is not supported in jMonkeyEngine.");
+				break;
+			case 3://Hemi
+				LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine.");
+				break;
+			case 4://Area
+				result = new DirectionalLight();
+				break;
+			default:
+				throw new BlenderFileException("Unknown light source type: " + type);
+		}
+		if(result != null) {
+			float r = ((Number)structure.getFieldValue("r")).floatValue();
+			float g = ((Number)structure.getFieldValue("g")).floatValue();
+			float b = ((Number)structure.getFieldValue("b")).floatValue();
+			result.setColor(new ColorRGBA(r, g, b, 0.0f));//TODO: 0 czy 1 ???
+		}
+		return result;
+	}
+}

+ 589 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MaterialHelper.java

@@ -0,0 +1,589 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+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.asset.BlenderKey.FeaturesToLoad;
+import com.jme3.material.MatParam;
+import com.jme3.material.Material;
+import com.jme3.material.Material.MatParamTexture;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+
+public class MaterialHelper extends AbstractBlenderHelper {
+	private static final Logger		LOGGER					= Logger.getLogger(MaterialHelper.class.getName());
+	protected static final float	DEFAULT_SHININESS		= 20.0f;
+
+	public static final String		TEXTURE_TYPE_COLOR		= "ColorMap";
+	public static final String		TEXTURE_TYPE_DIFFUSE	= "DiffuseMap";
+	public static final String		TEXTURE_TYPE_NORMAL		= "NormalMap";
+	public static final String		TEXTURE_TYPE_SPECULAR	= "SpecularMap";
+	public static final String		TEXTURE_TYPE_GLOW		= "GlowMap";
+	public static final String		TEXTURE_TYPE_ALPHA		= "AlphaMap";
+
+	/**
+	 * The type of the material's diffuse shader.
+	 */
+	public static enum DiffuseShader {
+		LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL
+	}
+
+	/**
+	 * The type of the material's specular shader.
+	 */
+	public static enum SpecularShader {
+		COOKTORRENCE, PHONG, BLINN, TOON, WARDISO
+	}
+
+	/** Face cull mode. Should be excplicitly set before this helper is used. */
+	protected FaceCullMode	faceCullMode;
+
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
+	 * versions.
+	 * 
+	 * @param blenderVersion
+	 *            the version read from the blend file
+	 */
+	public MaterialHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	/**
+	 * This method sets the face cull mode to be used with every loaded material.
+	 * 
+	 * @param faceCullMode
+	 *            the face cull mode
+	 */
+	public void setFaceCullMode(FaceCullMode faceCullMode) {
+		this.faceCullMode = faceCullMode;
+	}
+
+	@SuppressWarnings("unchecked")
+	public Material toMaterial(Structure structure, DataRepository dataRepository) throws BlenderFileException {
+		LOGGER.log(Level.INFO, "Loading material.");
+		if (structure == null) {
+			return dataRepository.getDefaultMaterial();
+		}
+		Material result = (Material) dataRepository.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+		if (result != null) {
+			return result;
+		}
+
+		int mode = ((Number) structure.getFieldValue("mode")).intValue();
+		boolean shadeless = (mode & 0x4) != 0;
+		boolean vertexColor = (mode & 0x16) != 0;
+		boolean transparent = (mode & 0x64) != 0;
+
+		if (shadeless) {
+			result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+		} else {
+			result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
+		}
+
+		result.getAdditionalRenderState().setFaceCullMode(faceCullMode);
+
+		if (transparent) {
+			result.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+		}
+
+		String name = structure.getName();
+		LOGGER.log(Level.INFO, "Material's name: {0}", name);
+		if (vertexColor) {
+			if (shadeless) {
+				result.setBoolean("VertexColor", true);
+			} else {
+				result.setBoolean("UseVertexColor", true);
+			}
+		}
+
+		MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+		ColorRGBA diffuseColor = null;
+		if (shadeless) {
+			// color of shadeless? doesn't seem to work in blender ..
+		} else {
+			result.setBoolean("UseMaterialColors", true);
+
+			// setting the colors
+			DiffuseShader diffuseShader = materialHelper.getDiffuseShader(structure);
+			result.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT);
+			diffuseColor = materialHelper.getDiffuseColor(structure, diffuseShader);
+			result.setColor("Diffuse", diffuseColor);
+
+			SpecularShader specularShader = materialHelper.getSpecularShader(structure);
+			result.setBoolean("WardIso", specularShader == SpecularShader.WARDISO);
+			result.setColor("Specular", materialHelper.getSpecularColor(structure, specularShader));
+
+			result.setColor("Ambient", materialHelper.getAmbientColor(structure));
+			result.setFloat("Shininess", materialHelper.getShininess(structure));
+		}
+
+		// texture
+		if ((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0) {
+			TextureHelper textureHelper = dataRepository.getHelper(TextureHelper.class);
+			DynamicArray<Pointer> mtexs = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
+			for (int i = 0; i < mtexs.getTotalSize(); ++i) {
+				Pointer p = mtexs.get(i);
+				if (!p.isNull()) {
+					List<Structure> mtex = p.fetchData(dataRepository.getInputStream());
+					if (mtex.size() == 1) {
+						Structure textureLink = mtex.get(0);
+						int texflag = ((Number)textureLink.getFieldValue("texflag")).intValue();
+						//int texco = ((Number) textureLink.getFieldValue("texco")).intValue();
+						boolean negateTexture = (texflag & 0x04)==0;
+						
+						// if(texco == 0x10) {//TEXCO_UV (this is only supported now)
+						int mapto = ((Number) textureLink.getFieldValue("mapto")).intValue();
+						if (mapto != 0) {
+							Pointer pTex = (Pointer) textureLink.getFieldValue("tex");
+							Structure tex = pTex.fetchData(dataRepository.getInputStream()).get(0);
+							Texture texture = textureHelper.getTexture(tex, dataRepository);
+							if (texture != null) {
+								if ((mapto & 0x01) != 0) {// Col
+									result.setBoolean("UseMaterialColors", Boolean.FALSE);
+									// blending the texture with material color and texture's defined color
+									int blendType = ((Number) textureLink.getFieldValue("blendtype")).intValue();
+									float[] color = new float[] { ((Number) textureLink.getFieldValue("r")).floatValue(),
+											((Number) textureLink.getFieldValue("g")).floatValue(),
+											((Number) textureLink.getFieldValue("b")).floatValue() };
+									float colfac = ((Number) textureLink.getFieldValue("colfac")).floatValue();
+									texture = textureHelper.blendTexture(diffuseColor.getColorArray(), texture, color, colfac, blendType,
+											negateTexture, dataRepository);
+									texture.setWrap(WrapMode.Repeat);
+									if (shadeless) {
+										result.setTexture(TEXTURE_TYPE_COLOR, texture);
+									} else {
+										result.setTexture(TEXTURE_TYPE_DIFFUSE, texture);
+									}
+								}
+								if ((mapto & 0x02) != 0) {// Nor
+									result.setTexture(TEXTURE_TYPE_NORMAL, texture);
+								}
+								if ((mapto & 0x20) != 0) {// Spec
+									result.setTexture(TEXTURE_TYPE_SPECULAR, texture);
+								}
+								if ((mapto & 0x40) != 0) {// Emit
+									result.setTexture(TEXTURE_TYPE_GLOW, texture);
+								}
+								if ((mapto & 0x80) != 0) {// Alpha
+									result.setTexture(TEXTURE_TYPE_ALPHA, texture);
+								}
+							} else {
+								LOGGER.log(Level.WARNING, "Texture not found!");
+							}
+						}
+						// } else {
+						// Pointer pTex = (Pointer)textureLink.getFieldValue("tex");
+						// List<Structure> texs = pTex.fetchData(dataRepository.getInputStream());
+						// Structure tex = texs.get(0);
+						// LOGGER.log(Level.WARNING, "Unsupported texture type: " + texco);
+						// }
+					} else {
+						LOGGER.log(Level.WARNING, "Many textures. Not solved yet!");// TODO
+					}
+				}
+			}
+		}
+		dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);
+		return result;
+	}
+
+	/**
+	 * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but
+	 * returned itself.
+	 * 
+	 * @param material
+	 *            a material to be cloned without textures
+	 * @param imageType
+	 *            type of image defined by blender; the constants are defined in TextureHelper
+	 * @return material without textures of a specified type
+	 */
+	public Material getNonTexturedMaterial(Material material, int imageType) {
+		String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR,
+				TEXTURE_TYPE_ALPHA };
+		Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length);
+		for (String textureParamName : textureParamNames) {
+			MatParamTexture matParamTexture = material.getTextureParam(textureParamName);
+			if (matParamTexture != null) {
+				textures.put(textureParamName, matParamTexture.getTextureValue());
+			}
+		}
+		if (textures.isEmpty()) {
+			return material;
+		} else {
+			// clear all textures first so that wo de not waste resources cloning them
+			for (Entry<String, Texture> textureParamName : textures.entrySet()) {
+				String name = textureParamName.getValue().getName();
+				try {
+					int type = Integer.parseInt(name);
+					if (type == imageType) {
+						material.clearParam(textureParamName.getKey());
+					}
+				} catch (NumberFormatException e) {
+					LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!",
+							name);
+				}
+			}
+			Material result = material.clone();
+			// put the textures back in place
+			for (Entry<String, Texture> textureEntry : textures.entrySet()) {
+				material.setTexture(textureEntry.getKey(), textureEntry.getValue());
+			}
+			return result;
+		}
+	}
+	
+	/**
+	 * This method converts the given material into particles-usable material.
+	 * The texture and glow color are being copied.
+	 * The method assumes it receives the Lighting type of material.
+	 * @param material the source material
+	 * @param dataRepository the data repository
+	 * @return material converted into particles-usable material
+	 */
+	public Material getParticlesMaterial(Material material, DataRepository dataRepository) {
+		Material result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
+		
+		//copying texture
+		MatParam diffuseMap = material.getParam("DiffuseMap");
+		if(diffuseMap!=null) {
+			Texture texture = (Texture) diffuseMap.getValue();
+			result.setTextureParam("Texture", VarType.Texture2D, texture);
+		}
+		
+		//copying glow color
+		MatParam glowColor = material.getParam("GlowColor");
+		if(glowColor!=null) {
+			ColorRGBA color = (ColorRGBA) glowColor.getValue();
+			result.setParam("GlowColor", VarType.Vector3, color);
+		}
+		//material.setTexture("Texture", dataRepository.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
+		return result;
+	}
+
+	/**
+	 * This method indicates if the material has a texture of a specified type.
+	 * 
+	 * @param material
+	 *            the material
+	 * @param textureType
+	 *            the type of the texture
+	 * @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise
+	 */
+	public boolean hasTexture(Material material, String textureType) {
+		if (material != null) {
+			return material.getTextureParam(textureType) != null;
+		}
+		return false;
+	}
+
+	/**
+	 * This method returns the diffuse color
+	 * 
+	 * @param materialStructure
+	 * @param diffuseShader
+	 * @return
+	 */
+	public ColorRGBA getDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) {
+		// bitwise 'or' of all textures mappings
+		int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue();
+
+		// diffuse color
+		float r = ((Number) materialStructure.getFieldValue("r")).floatValue();
+		float g = ((Number) materialStructure.getFieldValue("g")).floatValue();
+		float b = ((Number) materialStructure.getFieldValue("b")).floatValue();
+		float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
+		if ((commonMapto & 0x01) == 0x01) {// Col
+			return new ColorRGBA(r, g, b, alpha);
+		} else {
+			switch (diffuseShader) {
+				case FRESNEL:
+				case ORENNAYAR:
+				case TOON:
+					break;// TODO: find what is the proper modification
+				case MINNAERT:
+				case LAMBERT:// TODO: check if that is correct
+					float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue();
+					r *= ref;
+					g *= ref;
+					b *= ref;
+					break;
+				default:
+					throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString());
+			}
+			return new ColorRGBA(r, g, b, alpha);
+		}
+	}
+
+	/**
+	 * This method returns an enum describing the type of a diffuse shader used by this material.
+	 * 
+	 * @param materialStructure
+	 *            the material structure filled with data
+	 * @return an enum describing the type of a diffuse shader used by this material
+	 */
+	public DiffuseShader getDiffuseShader(Structure materialStructure) {
+		int diff_shader = ((Number) materialStructure.getFieldValue("diff_shader")).intValue();
+		return DiffuseShader.values()[diff_shader];
+	}
+
+	/**
+	 * This method returns an ambient color used by the material.
+	 * 
+	 * @param materialStructure
+	 *            the material structure filled with data
+	 * @return an ambient color used by the material
+	 */
+	public ColorRGBA getAmbientColor(Structure materialStructure) {
+		float r = ((Number) materialStructure.getFieldValue("ambr")).floatValue();
+		float g = ((Number) materialStructure.getFieldValue("ambg")).floatValue();
+		float b = ((Number) materialStructure.getFieldValue("ambb")).floatValue();
+		float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
+		return new ColorRGBA(r, g, b, alpha);
+	}
+
+	/**
+	 * This method returns an enum describing the type of a specular shader used by this material.
+	 * 
+	 * @param materialStructure
+	 *            the material structure filled with data
+	 * @return an enum describing the type of a specular shader used by this material
+	 */
+	public SpecularShader getSpecularShader(Structure materialStructure) {
+		int spec_shader = ((Number) materialStructure.getFieldValue("spec_shader")).intValue();
+		return SpecularShader.values()[spec_shader];
+	}
+
+	/**
+	 * This method returns a specular color used by the material.
+	 * 
+	 * @param materialStructure
+	 *            the material structure filled with data
+	 * @return a specular color used by the material
+	 */
+	public ColorRGBA getSpecularColor(Structure materialStructure, SpecularShader specularShader) {
+		float r = ((Number) materialStructure.getFieldValue("specr")).floatValue();
+		float g = ((Number) materialStructure.getFieldValue("specg")).floatValue();
+		float b = ((Number) materialStructure.getFieldValue("specb")).floatValue();
+		float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
+		switch (specularShader) {
+			case BLINN:
+			case COOKTORRENCE:
+			case TOON:
+			case WARDISO:// TODO: find what is the proper modification
+				break;
+			case PHONG:// TODO: check if that is correct
+				float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue();
+				r *= spec * 0.5f;
+				g *= spec * 0.5f;
+				b *= spec * 0.5f;
+				break;
+			default:
+				throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString());
+		}
+		return new ColorRGBA(r, g, b, alpha);
+	}
+
+	/**
+	 * This method returns the sihiness of this material or DEFAULT_SHININESS value if not present.
+	 * 
+	 * @param materialStructure
+	 *            the material structure filled with data
+	 * @return the sihiness of this material or DEFAULT_SHININESS value if not present
+	 */
+	public float getShininess(Structure materialStructure) {
+		float shininess = ((Number) materialStructure.getFieldValue("emit")).floatValue();
+		return shininess > 0.0f ? shininess : DEFAULT_SHININESS;
+	}
+
+	/**
+	 * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or
+	 * curve) but needs to have 'mat' field/
+	 * 
+	 * @param structureWithMaterials
+	 *            the structure containing the mesh data
+	 * @param dataRepository
+	 *            the data repository
+	 * @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
+	 */
+	public Material[] getMaterials(Structure structureWithMaterials, DataRepository dataRepository) throws BlenderFileException {
+		Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat");
+		Material[] materials = null;
+		if (!ppMaterials.isNull()) {
+			List<Structure> materialStructures = ppMaterials.fetchData(dataRepository.getInputStream());
+			if (materialStructures != null && materialStructures.size() > 0) {
+				MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+				materials = new Material[materialStructures.size()];
+				int i = 0;
+				for (Structure s : materialStructures) {
+					Material material = (Material) dataRepository.getLoadedFeature(s.getOldMemoryAddress(),
+							LoadedFeatureDataType.LOADED_FEATURE);
+					if (material == null) {
+						material = materialHelper.toMaterial(s, dataRepository);
+					}
+					materials[i++] = material;
+				}
+			}
+		}
+		return materials;
+	}
+
+	/**
+	 * This method converts rgb values to hsv values.
+	 * 
+	 * @param rgb
+	 *            rgb values of the color
+	 * @param hsv
+	 *            hsv values of a color (this table contains the result of the transformation)
+	 */
+	public void rgbToHsv(float r, float g, float b, float[] hsv) {
+		float cmax = r;
+		float cmin = r;
+		cmax = g > cmax ? g : cmax;
+		cmin = g < cmin ? g : cmin;
+		cmax = b > cmax ? b : cmax;
+		cmin = b < cmin ? b : cmin;
+
+		hsv[2] = cmax; /* value */
+		if (cmax != 0.0) {
+			hsv[1] = (cmax - cmin) / cmax;
+		} else {
+			hsv[1] = 0.0f;
+			hsv[0] = 0.0f;
+		}
+		if (hsv[1] == 0.0) {
+			hsv[0] = -1.0f;
+		} else {
+			float cdelta = cmax - cmin;
+			float rc = (cmax - r) / cdelta;
+			float gc = (cmax - g) / cdelta;
+			float bc = (cmax - b) / cdelta;
+			if (r == cmax) {
+				hsv[0] = bc - gc;
+			} else if (g == cmax) {
+				hsv[0] = 2.0f + rc - bc;
+			} else {
+				hsv[0] = 4.0f + gc - rc;
+			}
+			hsv[0] *= 60.0f;
+			if (hsv[0] < 0.0f) {
+				hsv[0] += 360.0f;
+			}
+		}
+
+		hsv[0] /= 360.0f;
+		if (hsv[0] < 0.0f) {
+			hsv[0] = 0.0f;
+		}
+	}
+
+	/**
+	 * This method converts rgb values to hsv values.
+	 * 
+	 * @param h
+	 *            hue
+	 * @param s
+	 *            saturation
+	 * @param v
+	 *            value
+	 * @param rgb
+	 *            rgb result vector (should have 3 elements)
+	 */
+	public void hsvToRgb(float h, float s, float v, float[] rgb) {
+		h *= 360.0f;
+		if (s == 0.0) {
+			rgb[0] = rgb[1] = rgb[2] = v;
+		} else {
+			if (h == 360) {
+				h = 0;
+			} else {
+				h /= 60;
+			}
+			int i = (int) Math.floor(h);
+			float f = h - i;
+			float p = v * (1.0f - s);
+			float q = v * (1.0f - s * f);
+			float t = v * (1.0f - s * (1.0f - f));
+			switch (i) {
+				case 0:
+					rgb[0] = v;
+					rgb[1] = t;
+					rgb[2] = p;
+					break;
+				case 1:
+					rgb[0] = q;
+					rgb[1] = v;
+					rgb[2] = p;
+					break;
+				case 2:
+					rgb[0] = p;
+					rgb[1] = v;
+					rgb[2] = t;
+					break;
+				case 3:
+					rgb[0] = p;
+					rgb[1] = q;
+					rgb[2] = v;
+					break;
+				case 4:
+					rgb[0] = t;
+					rgb[1] = p;
+					rgb[2] = v;
+					break;
+				case 5:
+					rgb[0] = v;
+					rgb[1] = p;
+					rgb[2] = q;
+					break;
+			}
+		}
+	}
+}

+ 599 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java

@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.jme3.asset.BlenderKey.FeaturesToLoad;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+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.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+import com.jme3.texture.Texture;
+import com.jme3.util.BufferUtils;
+
+/**
+ * A class that is used in mesh calculations.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class MeshHelper extends AbstractBlenderHelper {
+	protected static final int	MAXIMUM_WEIGHTS_PER_VERTEX	= 4;	// have no idea why 4, could someone please explain ?
+
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
+	 * versions.
+	 * 
+	 * @param blenderVersion
+	 *            the version read from the blend file
+	 */
+	public MeshHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	/**
+	 * This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data.
+	 * 
+	 * @param structure
+	 *            the structure we read the mesh from
+	 * @return the mesh feature
+	 * @throws BlenderFileException
+	 */
+	@SuppressWarnings("unchecked")
+	public List<Geometry> toMesh(Structure structure, DataRepository dataRepository) throws BlenderFileException {
+		List<Geometry> geometries = (List<Geometry>) dataRepository.getLoadedFeature(structure.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;
+		}
+
+		// helpers
+		TextureHelper textureHelper = dataRepository.getHelper(TextureHelper.class);
+
+		// reading mesh data
+		String name = structure.getName();
+
+		// reading vertices
+		Vector3f[] vertices = this.getVertices(structure, dataRepository);
+		int verticesAmount = vertices.length;
+
+		// vertices Colors
+		List<float[]> verticesColors = this.getVerticesColors(structure, dataRepository);
+
+		// reading faces
+		// the following map sorts faces by material number (because in jme Mesh can have only one material)
+		Map<Integer, List<Integer>> meshesMap = new HashMap<Integer, List<Integer>>();
+		Pointer pMFace = (Pointer) structure.getFieldValue("mface");
+		List<Structure> mFaces = pMFace.fetchData(dataRepository.getInputStream());
+
+		Pointer pMTFace = (Pointer) structure.getFieldValue("mtface");
+		List<Vector2f> uvCoordinates = null;
+		List<Structure> mtFaces = null;
+
+		if (!pMTFace.isNull()) {
+			mtFaces = pMTFace.fetchData(dataRepository.getInputStream());
+			int facesAmount = ((Number) structure.getFieldValue("totface")).intValue();
+			if (mtFaces.size() != facesAmount) {
+				throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!");
+			}
+			uvCoordinates = new ArrayList<Vector2f>();// TODO: calculate the amount of coordinates if possible
+		}
+
+		// normalMap merges normals of faces that will be rendered smooth
+		Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(verticesAmount);
+
+		List<Vector3f> normalList = new ArrayList<Vector3f>();
+		List<Vector3f> vertexList = new ArrayList<Vector3f>();
+		Map<Integer, Texture> materialNumberToTexture = new HashMap<Integer, Texture>();// indicates if the material with the specified
+																						// number should have a texture attached
+		// 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)
+		Map<Integer, List<Integer>> vertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAmount);
+		int vertexColorIndex = 0;
+		for (int i = 0; i < mFaces.size(); ++i) {
+			Structure mFace = mFaces.get(i);
+			boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
+			DynamicArray<Number> uvs = null;
+			boolean materialWithoutTextures = false;
+			Pointer pImage = null;
+			if (mtFaces != null) {
+				Structure mtFace = mtFaces.get(i);
+				pImage = (Pointer) mtFace.getFieldValue("tpage");
+				materialWithoutTextures = pImage.isNull();
+				// uvs always must be added wheater we have texture or not
+				uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv");
+				uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));
+				uvCoordinates.add(new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue()));
+				uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));
+			}
+			int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue();
+			Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr);
+			List<Integer> indexList = meshesMap.get(materialNumber);
+			if (indexList == null) {
+				indexList = new ArrayList<Integer>();
+				meshesMap.put(materialNumber, indexList);
+			}
+			if (pImage != null && !pImage.isNull() && !materialNumberToTexture.containsKey(materialNumber)) {// attaching image to texture
+																												// (face can have UV's and
+																												// image whlie its material
+																												// may have no texture
+																												// attached)
+				Texture texture = textureHelper.getTextureFromImage(pImage.fetchData(dataRepository.getInputStream()).get(0),
+						dataRepository);
+				if (texture != null) {
+					materialNumberToTexture.put(materialNumber, texture);
+				}
+			}
+
+			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();
+
+			Vector3f n = FastMath.computeNormal(vertices[v1], vertices[v2], vertices[v3]);
+			this.addNormal(n, normalMap, smooth, vertices[v1], vertices[v2], vertices[v3]);
+			normalList.add(normalMap.get(vertices[v1]));
+			normalList.add(normalMap.get(vertices[v2]));
+			normalList.add(normalMap.get(vertices[v3]));
+
+			this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);
+			indexList.add(vertexList.size());
+			vertexList.add(vertices[v1]);
+
+			this.appendVertexReference(v2, vertexList.size(), vertexReferenceMap);
+			indexList.add(vertexList.size());
+			vertexList.add(vertices[v2]);
+
+			this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);
+			indexList.add(vertexList.size());
+			vertexList.add(vertices[v3]);
+
+			if (v4 > 0) {
+				if (uvs != null) {
+					uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()));
+					uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()));
+					uvCoordinates.add(new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue()));
+				}
+				this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);
+				indexList.add(vertexList.size());
+				vertexList.add(vertices[v1]);
+
+				this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);
+				indexList.add(vertexList.size());
+				vertexList.add(vertices[v3]);
+
+				this.appendVertexReference(v4, vertexList.size(), vertexReferenceMap);
+				indexList.add(vertexList.size());
+				vertexList.add(vertices[v4]);
+
+				this.addNormal(n, normalMap, smooth, vertices[v4]);
+				normalList.add(normalMap.get(vertices[v1]));
+				normalList.add(normalMap.get(vertices[v3]));
+				normalList.add(normalMap.get(vertices[v4]));
+
+				if (verticesColors != null) {
+					verticesColors.add(vertexColorIndex + 3, verticesColors.get(vertexColorIndex));
+					verticesColors.add(vertexColorIndex + 4, verticesColors.get(vertexColorIndex + 2));
+				}
+				vertexColorIndex += 6;
+			} else {
+				if (verticesColors != null) {
+					verticesColors.remove(vertexColorIndex + 3);
+					vertexColorIndex += 3;
+				}
+			}
+		}
+		Vector3f[] normals = normalList.toArray(new Vector3f[normalList.size()]);
+
+		// reading vertices groups (from the parent)
+		Structure parent = dataRepository.peekParent();
+		Structure defbase = (Structure) parent.getFieldValue("defbase");
+		List<Structure> defs = defbase.evaluateListBase(dataRepository);
+		String[] verticesGroups = new String[defs.size()];
+		int defIndex = 0;
+		for (Structure def : defs) {
+			verticesGroups[defIndex++] = def.getFieldValue("name").toString();
+		}
+
+		// vertices bone weights and indices
+		ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
+		Structure defBase = (Structure) parent.getFieldValue("defbase");
+		Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, dataRepository);
+
+		VertexBuffer verticesWeights = null, verticesWeightsIndices = null;
+		int[] bonesGroups = new int[] { 0 };
+		VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(structure, vertexList.size(), bonesGroups,
+				vertexReferenceMap, groupToBoneIndexMap, dataRepository);
+		verticesWeights = boneWeightsAndIndex[0];
+		verticesWeightsIndices = boneWeightsAndIndex[1];
+
+		// reading materials
+		MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+		Material[] materials = null;
+		Material[] nonTexturedMaterials = null;
+		if ((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
+			materials = materialHelper.getMaterials(structure, dataRepository);
+			nonTexturedMaterials = materials == null ? null : new Material[materials.length];// fill it when needed
+		}
+
+		// creating the result meshes
+		geometries = new ArrayList<Geometry>(meshesMap.size());
+
+		VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
+		verticesBuffer.setupData(Usage.Stream, 3, Format.Float,
+				BufferUtils.createFloatBuffer(vertexList.toArray(new Vector3f[vertexList.size()])));
+
+		// initial vertex position (used with animation)
+		VertexBuffer verticesBind = new VertexBuffer(Type.BindPosePosition);
+		verticesBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(verticesBuffer.getData()));
+
+		VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
+		normalsBuffer.setupData(Usage.Stream, 3, Format.Float, BufferUtils.createFloatBuffer(normals));
+
+		// initial normals position (used with animation)
+		VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);
+		normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData()));
+
+		VertexBuffer uvCoordsBuffer = null;
+		if (uvCoordinates != null) {
+			uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
+			uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float,
+					BufferUtils.createFloatBuffer(uvCoordinates.toArray(new Vector2f[uvCoordinates.size()])));
+		}
+
+		// generating meshes
+		FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors);
+		for (Entry<Integer, List<Integer>> meshEntry : meshesMap.entrySet()) {
+			Mesh mesh = new Mesh();
+
+			// creating vertices indices for this mesh
+			List<Integer> indexList = meshEntry.getValue();
+			int[] indices = new int[indexList.size()];
+			for (int i = 0; i < indexList.size(); ++i) {
+				indices[i] = indexList.get(i).intValue();
+			}
+
+			// setting vertices
+			mesh.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indices));
+			mesh.setBuffer(verticesBuffer);
+			mesh.setBuffer(verticesBind);
+
+			// setting vertices colors
+			if (verticesColorsBuffer != null) {
+				mesh.setBuffer(Type.Color, 4, verticesColorsBuffer);
+			}
+
+			// setting weights for bones
+			if (verticesWeights != null) {
+				mesh.setMaxNumWeights(bonesGroups[0]);
+				mesh.setBuffer(verticesWeights);
+				mesh.setBuffer(verticesWeightsIndices);
+			}
+
+			// setting faces' normals
+			mesh.setBuffer(normalsBuffer);
+			mesh.setBuffer(normalsBind);
+
+			// setting uvCoords
+			if (uvCoordsBuffer != null) {
+				mesh.setBuffer(uvCoordsBuffer);
+			}
+
+			// creating the result
+			Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);
+			if (materials != null) {
+				int materialNumber = meshEntry.getKey().intValue();
+				Material material;
+				if (materialNumber >= 0) {
+					material = materials[materialNumber];
+					if (materialNumberToTexture.containsKey(Integer.valueOf(materialNumber))) {
+						if (material.getMaterialDef().getAssetName().contains("Lighting")) {
+							if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_DIFFUSE)) {
+								material = material.clone();
+								material.setTexture(MaterialHelper.TEXTURE_TYPE_DIFFUSE,
+										materialNumberToTexture.get(Integer.valueOf(materialNumber)));
+							}
+						} else {
+							if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_COLOR)) {
+								material = material.clone();
+								material.setTexture(MaterialHelper.TEXTURE_TYPE_COLOR,
+										materialNumberToTexture.get(Integer.valueOf(materialNumber)));
+							}
+						}
+					}
+				} else {
+					materialNumber = -1 * (materialNumber + 1);
+					if (nonTexturedMaterials[materialNumber] == null) {
+						nonTexturedMaterials[materialNumber] = materialHelper.getNonTexturedMaterial(materials[materialNumber],
+								TextureHelper.TEX_IMAGE);
+					}
+					material = nonTexturedMaterials[materialNumber];
+				}
+				geometry.setMaterial(material);
+			} else {
+				geometry.setMaterial(dataRepository.getDefaultMaterial());
+			}
+			geometries.add(geometry);
+		}
+		dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries);
+		return geometries;
+	}
+
+	/**
+	 * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.
+	 * 
+	 * @param normalToAdd
+	 *            a normal to be added
+	 * @param normalMap
+	 *            merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector
+	 * @param smooth
+	 *            the variable that indicates wheather to merge normals (creating the smooth mesh) or not
+	 * @param vertices
+	 *            a list of vertices read from the blender file
+	 */
+	protected void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {
+		for (Vector3f v : vertices) {
+			Vector3f n = normalMap.get(v);
+			if (!smooth || n == null) {
+				normalMap.put(v, normalToAdd.clone());
+			} else {
+				n.addLocal(normalToAdd).normalizeLocal();
+			}
+		}
+	}
+
+	/**
+	 * 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
+	 */
+	protected 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));
+	}
+
+	/**
+	 * This method returns the vertices colors. Each vertex is stored in float[4] array.
+	 * 
+	 * @param meshStructure
+	 *            the structure containing the mesh data
+	 * @param dataRepository
+	 *            the data repository
+	 * @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
+	 */
+	public List<float[]> getVerticesColors(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException {
+		Pointer pMCol = (Pointer) meshStructure.getFieldValue("mcol");
+		List<float[]> verticesColors = null;
+		List<Structure> mCol = null;
+		if (!pMCol.isNull()) {
+			verticesColors = new LinkedList<float[]>();
+			mCol = pMCol.fetchData(dataRepository.getInputStream());
+			for (Structure color : mCol) {
+				float r = ((Number) color.getFieldValue("r")).byteValue() / 256.0f;
+				float g = ((Number) color.getFieldValue("g")).byteValue() / 256.0f;
+				float b = ((Number) color.getFieldValue("b")).byteValue() / 256.0f;
+				float a = ((Number) color.getFieldValue("a")).byteValue() / 256.0f;
+				verticesColors.add(new float[] { b, g, r, a });
+			}
+		}
+		return verticesColors;
+	}
+
+	/**
+	 * This method returns the vertices.
+	 * 
+	 * @param meshStructure
+	 *            the structure containing the mesh data
+	 * @param dataRepository
+	 *            the data repository
+	 * @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
+	 */
+	@SuppressWarnings("unchecked")
+	public Vector3f[] getVertices(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException {
+		int verticesAmount = ((Number) meshStructure.getFieldValue("totvert")).intValue();
+		Vector3f[] vertices = new Vector3f[verticesAmount];
+		Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");
+		List<Structure> mVerts = pMVert.fetchData(dataRepository.getInputStream());
+		for (int i = 0; i < verticesAmount; ++i) {
+			DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
+			vertices[i] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
+		}
+		return vertices;
+	}
+
+	/**
+	 * This method returns an array of size 2. The first element is a vertex buffer holding bone weights for every vertex in the model. The
+	 * second element is a vertex buffer holding bone indices for vertices (the indices of bones the vertices are assigned to).
+	 * 
+	 * @param meshStructure
+	 *            the mesh structure object
+	 * @param vertexListSize
+	 *            a number of vertices in the model
+	 * @param bonesGroups
+	 *            this is an output parameter, it should be a one-sized array; the maximum amount of weights per vertex (up to
+	 *            MAXIMUM_WEIGHTS_PER_VERTEX) is stored there
+	 * @param vertexReferenceMap
+	 *            this reference map allows to map the original vertices read from blender to vertices that are really in the model; one
+	 *            vertex may appear several times in the result model
+	 * @param groupToBoneIndexMap
+	 *            this object maps the group index (to which a vertices in blender belong) to bone index of the model
+	 * @param dataRepository
+	 *            the data repository
+	 * @return arrays of vertices weights and their bone indices and (as an outpot parameter) the maximum amount of weights for a vertex
+	 * @throws BlenderFileException
+	 *             this exception is thrown when the blend file structure is somehow invalid or corrupted
+	 */
+	public VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups,
+			Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, DataRepository dataRepository)
+			throws BlenderFileException {
+		Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
+		FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
+		ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
+		if (!pDvert.isNull()) {// assigning weights and bone indices
+			List<Structure> dverts = pDvert.fetchData(dataRepository.getInputStream());// dverts.size() == verticesAmount (one dvert per
+																						// vertex in blender)
+			int vertexIndex = 0;
+			for (Structure dvert : dverts) {
+				int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex
+																						// (max. 4 in JME)
+				Pointer pDW = (Pointer) dvert.getFieldValue("dw");
+				List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here
+				if (totweight > 0 && !pDW.isNull()) {// pDW should never be null here, but I check it just in case :)
+					int weightIndex = 0;
+					List<Structure> dw = pDW.fetchData(dataRepository.getInputStream());
+					for (Structure deformWeight : dw) {
+						Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
+						if (boneIndex != null) {// null here means that we came accross group that has no bone attached to
+							float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
+							if (weight == 0.0f) {
+								weight = 1;
+								boneIndex = Integer.valueOf(0);
+							}
+							// we apply the weight to all referenced vertices
+							for (Integer index : vertexIndices) {
+								// all indices are always assigned to 0-indexed bone
+								// weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, 1.0f);
+								// indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, (byte)0);
+								// if(weight != 0.0f) {
+								weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
+								indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
+								// }
+							}
+						}
+						++weightIndex;
+					}
+				} else {
+					for (Integer index : vertexIndices) {
+						weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
+						indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
+					}
+				}
+				++vertexIndex;
+			}
+		} else {
+			// always bind all vertices to 0-indexed bone
+			// this bone makes the model look normally if vertices have no bone assigned
+			// and it is used in object animation, so if we come accross object animation
+			// we can use the 0-indexed bone for this
+			for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {
+				// we apply the weight to all referenced vertices
+				for (Integer index : vertexIndexList) {
+					weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
+					indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
+				}
+			}
+		}
+
+		bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData);
+		VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
+		verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);
+
+		VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
+		verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);
+		return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };
+	}
+
+	/**
+	 * Normalizes weights if needed and finds largest amount of weights used for all vertices in the buffer.
+	 */
+	protected int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {
+		int maxWeightsPerVert = 0;
+		weightsFloatData.rewind();
+		for (int v = 0; v < vertCount; ++v) {
+			float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get();
+
+			if (w3 != 0) {
+				maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
+			} else if (w2 != 0) {
+				maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
+			} else if (w1 != 0) {
+				maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
+			} else if (w0 != 0) {
+				maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
+			}
+
+			float sum = w0 + w1 + w2 + w3;
+			if (sum != 1f && sum != 0.0f) {
+				weightsFloatData.position(weightsFloatData.position() - 4);
+				// compute new vals based on sum
+				float sumToB = 1f / sum;
+				weightsFloatData.put(w0 * sumToB);
+				weightsFloatData.put(w1 * sumToB);
+				weightsFloatData.put(w2 * sumToB);
+				weightsFloatData.put(w3 * sumToB);
+			}
+		}
+		weightsFloatData.rewind();
+
+		// mesh.setMaxNumWeights(maxWeightsPerVert);
+		return maxWeightsPerVert;
+	}
+}

+ 527 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ModifierHelper.java

@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneAnimation;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.animation.SkeletonControl;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.effect.EmitterMeshVertexShape;
+import com.jme3.effect.EmitterShape;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Quaternion;
+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.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.helpers.ParticlesHelper;
+import com.jme3.scene.plugins.blender.structures.Constraint;
+import com.jme3.scene.plugins.blender.structures.Modifier;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+import com.jme3.scene.plugins.ogre.AnimData;
+
+/**
+ * A class that is used in modifiers calculations.
+ * @author Marcin Roguski
+ */
+public class ModifierHelper extends AbstractBlenderHelper {
+	private static final Logger			LOGGER		= Logger.getLogger(ModifierHelper.class.getName());
+	
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ModifierHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	/**
+	 * This method applies modifier to the object.
+	 * @param node
+	 *        the loaded object
+	 * @param modifier
+	 *        the modifier to apply
+	 * @param dataRepository
+	 *        the data repository
+	 * @return the node to whom the modifier was applied
+	 */
+	public Node applyModifier(Node node, Modifier modifier, DataRepository dataRepository) {
+		if(Modifier.ARMATURE_MODIFIER_DATA.equals(modifier.getType())) {
+			return this.applyArmatureModifierData(node, modifier, dataRepository);
+		} else if(Modifier.ARRAY_MODIFIER_DATA.equals(modifier.getType())) {
+			return this.applyArrayModifierData(node, modifier, dataRepository);
+		} else if(Modifier.PARTICLE_MODIFIER_DATA.equals(modifier.getType())) {
+			return this.applyParticleSystemModifierData(node, modifier, dataRepository);
+		} else {
+			LOGGER.warning("Modifier: " + modifier.getType() + " not yet implemented!!!");
+			return node;
+		}
+	}
+	
+	/**
+	 * This method reads the given object's modifiers.
+	 * @param objectStructure
+	 *        the object structure
+	 * @param dataRepository
+	 *        the data repository
+	 * @param converter
+	 *        the converter object (in some cases we need to read an object first before loading the modifier)
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blender file is somehow corrupted
+	 */
+	@SuppressWarnings("unchecked")
+	public void readModifiers(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
+		Structure modifiersListBase = (Structure)objectStructure.getFieldValue("modifiers");
+		List<Structure> modifiers = modifiersListBase.evaluateListBase(dataRepository);
+		for(Structure modifier : modifiers) {
+			Object loadedModifier = null;
+			Object modifierAdditionalData = null;
+			if(Modifier.ARRAY_MODIFIER_DATA.equals(modifier.getType())) {//****************ARRAY MODIFIER
+				Map<String, Object> params = new HashMap<String, Object>();
+				
+				Number fittype = (Number) modifier.getFieldValue("fit_type");
+				params.put("fittype", fittype);
+				switch(fittype.intValue()) {
+					case 0://FIXED COUNT
+						params.put("count", modifier.getFieldValue("count"));
+						break;
+					case 1://FIXED LENGTH
+						params.put("length", modifier.getFieldValue("length"));
+						break;
+					case 2://FITCURVE
+						//TODO: implement after loading curves is added; warning will be generated during modifier applying
+						break;
+					default:
+						assert false : "Unknown array modifier fit type: " + fittype;
+				}
+				
+				//offset parameters
+				int offsettype = ((Number) modifier.getFieldValue("offset_type")).intValue();
+				if((offsettype & 0x01) != 0) {//Constant offset
+					DynamicArray<Number> offsetArray = (DynamicArray<Number>)modifier.getFieldValue("offset");
+					float[] offset = new float[] {offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue()};
+					params.put("offset", offset);
+				}
+				if((offsettype & 0x02) != 0) {//Relative offset
+					DynamicArray<Number> scaleArray = (DynamicArray<Number>)modifier.getFieldValue("scale");
+					float[] scale = new float[] {scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()};
+					params.put("scale", scale);
+				}
+				if((offsettype & 0x04) != 0) {//Object offset
+					Pointer pOffsetObject = (Pointer)modifier.getFieldValue("offset_ob");
+					if(!pOffsetObject.isNull()) {
+						params.put("offsetob", pOffsetObject);
+					}
+				}
+				
+				//start cap and end cap
+				Pointer pStartCap = (Pointer)modifier.getFieldValue("start_cap");
+				if(!pStartCap.isNull()) {
+					params.put("startcap", pStartCap);
+				}
+				Pointer pEndCap = (Pointer)modifier.getFieldValue("end_cap");
+				if(!pEndCap.isNull()) {
+					params.put("endcap", pEndCap);
+				}
+				loadedModifier = params;
+			} else if(Modifier.ARMATURE_MODIFIER_DATA.equals(modifier.getType())) {//****************ARMATURE MODIFIER
+				Pointer pArmatureObject = (Pointer)modifier.getFieldValue("object");
+				if(!pArmatureObject.isNull()) {
+					ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+					Structure armatureObject = (Structure)dataRepository.getLoadedFeature(pArmatureObject.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_STRUCTURE);
+					if(armatureObject == null) {//we check this first not to fetch the structure unnecessary
+						armatureObject = pArmatureObject.fetchData(dataRepository.getInputStream()).get(0);
+						objectHelper.toObject(armatureObject, dataRepository);
+					}
+					modifierAdditionalData = armatureObject.getOldMemoryAddress();
+					ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
+
+					//changing bones matrices so that they fit the current object (taht is why we need a copy of a skeleton)
+					Matrix4f armatureObjectMatrix = objectHelper.getTransformationMatrix(armatureObject);
+					Matrix4f inverseMeshObjectMatrix = objectHelper.getTransformationMatrix(objectStructure).invert();
+					Matrix4f additionalRootBoneTransformation = inverseMeshObjectMatrix.multLocal(armatureObjectMatrix);
+					Bone[] bones = armatureHelper.buildBonesStructure(Long.valueOf(0L), additionalRootBoneTransformation);
+
+					String objectName = objectStructure.getName();
+					Set<String> animationNames = dataRepository.getBlenderKey().getAnimationNames(objectName);
+					if(animationNames != null && animationNames.size() > 0) {
+						ArrayList<BoneAnimation> animations = new ArrayList<BoneAnimation>();
+						List<FileBlockHeader> actionHeaders = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
+						for(FileBlockHeader header : actionHeaders) {
+							Structure actionStructure = header.getStructure(dataRepository);
+							String actionName = actionStructure.getName();
+							if(animationNames.contains(actionName)) {
+								int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, actionName);
+								int fps = dataRepository.getBlenderKey().getFps();
+								float start = (float)animationFrames[0] / (float)fps;
+								float stop = (float)animationFrames[1] / (float)fps;
+								BoneAnimation boneAnimation = new BoneAnimation(actionName, stop - start);
+								boneAnimation.setTracks(armatureHelper.getTracks(actionStructure, dataRepository, objectName, actionName));
+								animations.add(boneAnimation);
+							}
+						}
+						loadedModifier = new AnimData(new Skeleton(bones), animations);
+					}
+				} else {
+					LOGGER.warning("Unsupported modifier type: " + modifier.getType());
+				}
+			} else if(Modifier.PARTICLE_MODIFIER_DATA.equals(modifier.getType())) {//****************PARTICLES MODIFIER
+				Pointer pParticleSystem = (Pointer) modifier.getFieldValue("psys");
+				if(!pParticleSystem.isNull()) {
+					ParticlesHelper particlesHelper = dataRepository.getHelper(ParticlesHelper.class);
+					Structure particleSystem = pParticleSystem.fetchData(dataRepository.getInputStream()).get(0);
+					loadedModifier = particlesHelper.toParticleEmitter(particleSystem, dataRepository);
+				}
+			}
+			//adding modifier to the modifier's lists
+			if(loadedModifier != null) {
+				dataRepository.addModifier(objectStructure.getOldMemoryAddress(), modifier.getType(), loadedModifier, modifierAdditionalData);
+				modifierAdditionalData = null;
+			}
+		}
+	}
+	
+	protected Node applyParticleSystemModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
+		MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+		ParticleEmitter emitter = (ParticleEmitter) modifier.getJmeModifierRepresentation();
+		emitter = emitter.clone();
+
+		//applying emitter shape
+		EmitterShape emitterShape = emitter.getShape();
+		if(emitterShape instanceof EmitterMeshVertexShape) {
+			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(), dataRepository);
+						emitter.setMaterial(material);//TODO: rozbić na kilka części
+					}
+				}
+			}
+			if(meshes.size()>0) {
+				((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
+			}
+		}
+		
+		node.attachChild(emitter);
+		return node;
+	}
+	
+	/**
+	 * This method applies ArmatureModifierData to the loaded object.
+	 * @param node
+	 *        the loaded object
+	 * @param modifier
+	 *        the modifier to apply
+	 * @param dataRepository
+	 *        the data repository
+	 * @return the node to whom the modifier was applied
+	 */
+	protected Node applyArmatureModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
+		AnimData ad = (AnimData)modifier.getJmeModifierRepresentation();
+		ArrayList<BoneAnimation> animList = ad.anims;
+		Long modifierArmatureObject = (Long)modifier.getAdditionalData();
+		if(animList != null && animList.size() > 0) {
+			ConstraintHelper constraintHelper = dataRepository.getHelper(ConstraintHelper.class);
+			Constraint[] constraints = constraintHelper.getConstraints(modifierArmatureObject);
+			HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>();
+			for(int i = 0; i < animList.size(); ++i) {
+				BoneAnimation boneAnimation = this.cloneBoneAnimation(animList.get(i));
+
+				//baking constraints into animations
+				if(constraints != null && constraints.length > 0) {
+					for(Constraint constraint : constraints) {
+						constraint.affectAnimation(ad.skeleton, boneAnimation);
+					}
+				}
+
+				anims.put(boneAnimation.getName(), boneAnimation);
+			}
+
+			//getting meshes
+			Mesh[] meshes = null;
+			List<Mesh> meshesList = new ArrayList<Mesh>();
+			List<Spatial> children = node.getChildren();
+			for(Spatial child : children) {
+				if(child instanceof Geometry) {
+					meshesList.add(((Geometry)child).getMesh());
+				}
+			}
+			if(meshesList.size() > 0) {
+				meshes = meshesList.toArray(new Mesh[meshesList.size()]);
+			}
+
+			//applying the control to the node
+			SkeletonControl skeletonControl = new SkeletonControl(meshes, ad.skeleton);
+			AnimControl control = node.getControl(AnimControl.class);
+			
+			if(control == null) {
+				control = new AnimControl(ad.skeleton);
+			} else {
+				//merging skeletons
+				Skeleton controlSkeleton = control.getSkeleton();
+				int boneIndexIncrease = controlSkeleton.getBoneCount();
+				Skeleton skeleton = this.merge(controlSkeleton, ad.skeleton);
+
+				//merging animations
+				HashMap<String, BoneAnimation> animations = new HashMap<String, BoneAnimation>();
+				for(String animationName : control.getAnimationNames()) {
+					animations.put(animationName, control.getAnim(animationName));
+				}
+				for(Entry<String, BoneAnimation> animEntry : anims.entrySet()) {
+					BoneAnimation ba = animEntry.getValue();
+					for(int i = 0; i < ba.getTracks().length; ++i) {
+						BoneTrack bt = ba.getTracks()[i];
+						int newBoneIndex = bt.getTargetBoneIndex() + boneIndexIncrease;
+						ba.getTracks()[i] = new BoneTrack(newBoneIndex, bt.getTimes(), bt.getTranslations(), bt.getRotations(), bt.getScales());
+					}
+					animations.put(animEntry.getKey(), animEntry.getValue());
+				}
+
+				//replacing the control
+				node.removeControl(control);
+				control = new AnimControl(skeleton);
+			}
+			control.setAnimations(anims);
+			node.addControl(control);
+			node.addControl(skeletonControl);
+		}
+		return node;
+	}
+	
+	/**
+	 * This method applies the array modifier to the node.
+	 * @param node
+	 *            the object the modifier will be applied to
+	 * @param modifier
+	 *            the modifier to be applied
+	 * @param dataRepository
+	 *            the data repository
+	 * @return object node with arry modifier applied
+	 */
+	@SuppressWarnings("unchecked")
+	protected Node applyArrayModifierData(Node node, Modifier modifier, DataRepository dataRepository) {
+		Map<String, Object> modifierData = (Map<String, Object>) modifier.getJmeModifierRepresentation();
+		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;
+			} 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 = dataRepository.getFileBlock(pOffsetObject.getOldMemoryAddress());
+			ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+			try {//we take the structure in case the object was not yet loaded
+				Structure offsetStructure = offsetObjectBlock.getStructure(dataRepository);
+				Vector3f translation = objectHelper.getTransformation(offsetStructure).getTranslation();
+				objectOffset[0] = translation.x;
+				objectOffset[1] = translation.y;
+				objectOffset[2] = translation.z;
+			} catch (BlenderFileException e) {
+				LOGGER.warning("Problems in blender file structure! Object offset cannot be applied! The problem: " + 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) dataRepository.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+				if(caps[i]!=null) {
+					caps[i] = (Node) caps[i].clone();
+				} else {
+					FileBlockHeader capBlock = dataRepository.getFileBlock(pOffsetObject.getOldMemoryAddress());
+					try {//we take the structure in case the object was not yet loaded
+						Structure capStructure = capBlock.getStructure(dataRepository);
+						ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+						caps[i] = (Node) objectHelper.toObject(capStructure, dataRepository);
+						if(caps[i]==null) {
+							LOGGER.warning("Cap object '" + capStructure.getName() + "' couldn't be loaded!");
+						}
+					} catch (BlenderFileException e) {
+						LOGGER.warning("Problems in blender file structure! Cap object cannot be applied! The problem: " + e.getMessage());
+					}
+				}
+			}
+		}
+		
+		Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], 
+												  offset[1] + scale[1] + objectOffset[1], 
+												  offset[2] + scale[2] + objectOffset[2]);
+		
+		//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;
+			}
+		} else if(fittype==2) {//Fit curve
+			LOGGER.warning("Fit curve mode in array modifier not yet implemented!");//TODO: implement fit curve
+		} else {
+			throw new IllegalStateException("Unknown fit type: " + fittype);
+		}
+		
+		//adding translated nodes and caps
+		if(count>0) {
+			Node[] arrayNodes = new Node[count];
+			Vector3f newTranslation = node.getLocalTranslation().clone();
+			for(int i=0;i<count;++i) {
+				newTranslation.addLocal(translationVector);
+				Node nodeClone = (Node) node.clone();
+				nodeClone.setLocalTranslation(newTranslation);
+				arrayNodes[i] = nodeClone;
+			}
+			for(Node nodeClone : arrayNodes) {
+				node.attachChild(nodeClone);
+			}
+			if(caps[0]!=null) {
+				caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector);
+				node.attachChild(caps[0]);
+			}
+			if(caps[1]!=null) {
+				caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector);
+				node.attachChild(caps[1]);
+			}
+		}
+		return node;
+	}
+	
+	/**
+	 * This class clones the bone animation data.
+	 * @param source
+	 *        the source that is to be cloned
+	 * @return the copy of the given bone animation
+	 */
+	protected BoneAnimation cloneBoneAnimation(BoneAnimation source) {
+		BoneAnimation result = new BoneAnimation(source.getName(), source.getLength());
+
+		//copying tracks and applying constraints
+		BoneTrack[] sourceTracks = source.getTracks();
+		BoneTrack[] boneTracks = new BoneTrack[sourceTracks.length];
+		for(int i = 0; i < sourceTracks.length; ++i) {
+			int tablesLength = sourceTracks[i].getTimes().length;
+
+			Vector3f[] sourceTranslations = sourceTracks[i].getTranslations();
+			Quaternion[] sourceRotations = sourceTracks[i].getRotations();
+			Vector3f[] sourceScales = sourceTracks[i].getScales();
+
+			Vector3f[] translations = new Vector3f[tablesLength];
+			Quaternion[] rotations = new Quaternion[tablesLength];
+			Vector3f[] scales = sourceScales == null ? null : new Vector3f[tablesLength];
+			for(int j = 0; j < tablesLength; ++j) {
+				translations[j] = sourceTranslations[j].clone();
+				rotations[j] = sourceRotations[j].clone();
+				if(sourceScales != null) {//only scales may not be applied
+					scales[j] = sourceScales[j].clone();
+				}
+			}
+			boneTracks[i] = new BoneTrack(sourceTracks[i].getTargetBoneIndex(), sourceTracks[i].getTimes(),//times do not change, no need to clone them,
+					translations, rotations, scales);
+		}
+		result.setTracks(boneTracks);
+		return result;
+	}
+	
+	/**
+	 * This method merges two skeletons into one. I assume that each skeleton's 0-indexed bone is objectAnimationBone so
+	 * only one such bone should be placed in the result
+	 * @param s1
+	 *        first skeleton
+	 * @param s2
+	 *        second skeleton
+	 * @return merged skeleton
+	 */
+	protected Skeleton merge(Skeleton s1, Skeleton s2) {
+		List<Bone> bones = new ArrayList<Bone>(s1.getBoneCount() + s2.getBoneCount());
+		for(int i = 0; i < s1.getBoneCount(); ++i) {
+			bones.add(s1.getBone(i));
+		}
+		for(int i = 1; i < s2.getBoneCount(); ++i) {//ommit objectAnimationBone
+			bones.add(s2.getBone(i));
+		}
+		return new Skeleton(bones.toArray(new Bone[bones.size()]));
+	}
+}

+ 1531 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/NoiseHelper.java

@@ -0,0 +1,1531 @@
+/*
+ *
+ * $Id: noise.c 14611 2008-04-29 08:24:33Z campbellbarton $
+ *
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.helpers.v249.TextureHelper.CBData;
+import com.jme3.scene.plugins.blender.helpers.v249.TextureHelper.ColorBand;
+import com.jme3.scene.plugins.blender.helpers.v249.TextureHelper.TexResult;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+
+/**
+ * Methods of this class are copied from blender 2.49 source code and modified so that they can be used in java. They
+ * are mostly NOT documented because they are not documented in blender's source code. If I find a proper description or
+ * discover what they actually do and what parameters mean - I shall describe such methods :) If anyone have some hint
+ * what these methods are doing please rite the proper javadoc documentation. These methods should be used to create
+ * generated textures.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class NoiseHelper extends AbstractBlenderHelper {
+	private static final Logger	LOGGER				= Logger.getLogger(NoiseHelper.class.getName());
+
+	/* return value */
+	protected static final int	TEX_INT				= 0;
+	protected static final int	TEX_RGB				= 1;
+	protected static final int	TEX_NOR				= 2;
+
+	/* noisetype */
+	protected static final int	TEX_NOISESOFT		= 0;
+	protected static final int	TEX_NOISEPERL		= 1;
+
+	/* tex->stype in texture.c - cloud types */
+	protected static final int	TEX_DEFAULT			= 0;
+	protected static final int	TEX_COLOR			= 1;
+
+	/* flag */
+	protected static final int	TEX_COLORBAND		= 1;
+	protected static final int	TEX_FLIPBLEND		= 2;
+	protected static final int	TEX_NEGALPHA		= 4;
+	protected static final int	TEX_CHECKER_ODD		= 8;
+	protected static final int	TEX_CHECKER_EVEN	= 16;
+	protected static final int	TEX_PRV_ALPHA		= 32;
+	protected static final int	TEX_PRV_NOR			= 64;
+	protected static final int	TEX_REPEAT_XMIR		= 128;
+	protected static final int	TEX_REPEAT_YMIR		= 256;
+	protected static final int	TEX_FLAG_MASK		= TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR;
+
+	/* tex->noisebasis2 in texture.c - wood waveforms */
+	protected static final int	TEX_SIN				= 0;
+	protected static final int	TEX_SAW				= 1;
+	protected static final int	TEX_TRI				= 2;
+
+	/* tex->stype in texture.c - marble types */
+	protected static final int	TEX_SOFT			= 0;
+	protected static final int	TEX_SHARP			= 1;
+	protected static final int	TEX_SHARPER			= 2;
+
+	/* tex->stype in texture.c - wood types */
+	protected static final int	TEX_BAND			= 0;
+	protected static final int	TEX_RING			= 1;
+	protected static final int	TEX_BANDNOISE		= 2;
+	protected static final int	TEX_RINGNOISE		= 3;
+
+	/* tex->stype in texture.c - blend types */
+	protected static final int	TEX_LIN				= 0;
+	protected static final int	TEX_QUAD			= 1;
+	protected static final int	TEX_EASE			= 2;
+	protected static final int	TEX_DIAG			= 3;
+	protected static final int	TEX_SPHERE			= 4;
+	protected static final int	TEX_HALO			= 5;
+	protected static final int	TEX_RAD				= 6;
+
+	/* tex->stype in texture.c - stucci types */
+	protected static final int	TEX_PLASTIC			= 0;
+	protected static final int	TEX_WALLIN			= 1;
+	protected static final int	TEX_WALLOUT			= 2;
+
+	/* musgrave stype */
+	protected static final int	TEX_MFRACTAL		= 0;
+	protected static final int	TEX_RIDGEDMF		= 1;
+	protected static final int	TEX_HYBRIDMF		= 2;
+	protected static final int	TEX_FBM				= 3;
+	protected static final int	TEX_HTERRAIN		= 4;
+
+	/* keyblock->type */
+	protected static final int	KEY_LINEAR			= 0;
+	protected static final int	KEY_CARDINAL		= 1;
+	protected static final int	KEY_BSPLINE			= 2;
+
+	/* CONSTANTS (read from file) */
+	protected static float[]	hashpntf;
+	protected static short[]	hash;
+	protected static float[]	hashvectf;
+	protected static short[]	p;
+	protected static float[][]	g;
+
+	/**
+	 * Constructor. Stores the blender version number and loads the constants needed for computations.
+	 * @param blenderVersion
+	 *        the number of blender version
+	 */
+	public NoiseHelper(String blenderVersion) {
+		super(blenderVersion);
+		this.loadConstants();
+	}
+
+	/**
+	 * This method loads the constants needed for computations. They are exactly like the ones the blender uses. Each
+	 * deriving class should override this method and load its own constraints. Be carefult with overriding though, if
+	 * an exception will be thrown the class will not be instantiated.
+	 */
+	protected void loadConstants() {
+		InputStream is = NoiseHelper.class.getResourceAsStream("noiseconstants.dat");
+		try {
+			ObjectInputStream ois = new ObjectInputStream(is);
+			hashpntf = (float[])ois.readObject();
+			hash = (short[])ois.readObject();
+			hashvectf = (float[])ois.readObject();
+			p = (short[])ois.readObject();
+			g = (float[][])ois.readObject();
+		} catch(IOException e) {
+			LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
+		} catch(ClassNotFoundException e) {
+			assert false : "Constants' classes should be arrays of primitive types, so they are ALWAYS known!";
+		} finally {
+			if(is != null) {
+				try {
+					is.close();
+				} catch(IOException e) {
+					LOGGER.log(Level.WARNING, e.getLocalizedMessage());
+				}
+			}
+		}
+	}
+
+	protected static Map<Integer, AbstractNoiseFunc>	noiseFunctions		= new HashMap<Integer, AbstractNoiseFunc>();
+	static {
+		// orgBlenderNoise (*Was BLI_hnoise(), removed noisesize, so other functions can call it without scaling.*)
+		noiseFunctions.put(Integer.valueOf(0), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				return this.orgBlenderNoise(x, y, z);
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				return 2.0f * this.orgBlenderNoise(x, y, z) - 1.0f;
+			}
+		});
+		// orgPerlinNoise (*For use with BLI_gNoise/gTurbulence, returns signed noise.*)
+		noiseFunctions.put(Integer.valueOf(1), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				return 0.5f + 0.5f * this.noise3Perlin(new float[] {x, y, z});
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				return this.noise3Perlin(new float[] {x, y, z});
+			}
+		});
+		// newPerlin (* for use with BLI_gNoise()/BLI_gTurbulence(), returns unsigned improved perlin noise *)
+		noiseFunctions.put(Integer.valueOf(2), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				return 0.5f + 0.5f * this.newPerlin(x, y, z);
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				return this.execute(x, y, z);
+			}
+		});
+		// voronoi_F1
+		noiseFunctions.put(Integer.valueOf(3), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return da[0];
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return 2.0f * da[0] - 1.0f;
+			}
+		});
+		// voronoi_F2
+		noiseFunctions.put(Integer.valueOf(4), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return da[1];
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return 2.0f * da[1] - 1.0f;
+			}
+		});
+		// voronoi_F3
+		noiseFunctions.put(Integer.valueOf(5), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return da[2];
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return 2.0f * da[2] - 1.0f;
+			}
+		});
+		// voronoi_F4
+		noiseFunctions.put(Integer.valueOf(6), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return da[3];
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return 2.0f * da[3] - 1.0f;
+			}
+		});
+		// voronoi_F1F2
+		noiseFunctions.put(Integer.valueOf(7), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return da[1] - da[0];
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				float[] da = new float[4], pa = new float[12];
+				AbstractNoiseFunc.voronoi(x, y, z, da, pa, 1, 0);
+				return 2.0f * (da[1] - da[0]) - 1.0f;
+			}
+		});
+		// voronoi_Cr
+		noiseFunctions.put(Integer.valueOf(8), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				float t = 10 * noiseFunctions.get(Integer.valueOf(7)).execute(x, y, z);// voronoi_F1F2
+				return t > 1.0f ? 1.0f : t;
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				float t = 10.0f * noiseFunctions.get(Integer.valueOf(7)).execute(x, y, z);// voronoi_F1F2
+				return t > 1.0f ? 1.0f : 2.0f * t - 1.0f;
+			}
+		});
+		// cellNoise
+		noiseFunctions.put(Integer.valueOf(14), new AbstractNoiseFunc() {
+			@Override
+			public float execute(float x, float y, float z) {
+				int xi = (int)Math.floor(x);
+				int yi = (int)Math.floor(y);
+				int zi = (int)Math.floor(z);
+				long n = xi + yi * 1301 + zi * 314159;
+				n ^= n << 13;
+				return (n * (n * n * 15731 + 789221) + 1376312589) / 4294967296.0f;
+			}
+
+			@Override
+			public float executeS(float x, float y, float z) {
+				return 2.0f * this.execute(x, y, z) - 1.0f;
+			}
+		});
+	}
+
+	/** Distance metrics for voronoi. e parameter only used in Minkovsky. */
+	protected static Map<Integer, IDistanceFunc>		distanceFunctions	= new HashMap<Integer, NoiseHelper.IDistanceFunc>();
+	static {
+		// real distance
+		distanceFunctions.put(Integer.valueOf(0), new IDistanceFunc() {
+			@Override
+			public float execute(float x, float y, float z, float e) {
+				return (float)Math.sqrt(x * x + y * y + z * z);
+			}
+		});
+		// distance squared
+		distanceFunctions.put(Integer.valueOf(1), new IDistanceFunc() {
+			@Override
+			public float execute(float x, float y, float z, float e) {
+				return x * x + y * y + z * z;
+			}
+		});
+		// manhattan/taxicab/cityblock distance
+		distanceFunctions.put(Integer.valueOf(2), new IDistanceFunc() {
+			@Override
+			public float execute(float x, float y, float z, float e) {
+				return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z);
+			}
+		});
+		// Chebychev
+		distanceFunctions.put(Integer.valueOf(3), new IDistanceFunc() {
+			@Override
+			public float execute(float x, float y, float z, float e) {
+				x = FastMath.abs(x);
+				y = FastMath.abs(y);
+				z = FastMath.abs(z);
+				float t = x > y ? x : y;
+				return z > t ? z : t;
+			}
+		});
+		// minkovsky preset exponent 0.5 (MinkovskyH)
+		distanceFunctions.put(Integer.valueOf(4), new IDistanceFunc() {
+			@Override
+			public float execute(float x, float y, float z, float e) {
+				float d = (float)(Math.sqrt(FastMath.abs(x)) + Math.sqrt(FastMath.abs(y)) + Math.sqrt(FastMath.abs(z)));
+				return d * d;
+			}
+		});
+		// minkovsky preset exponent 4 (Minkovsky4)
+		distanceFunctions.put(Integer.valueOf(5), new IDistanceFunc() {
+			@Override
+			public float execute(float x, float y, float z, float e) {
+				x *= x;
+				y *= y;
+				z *= z;
+				return (float)Math.sqrt(Math.sqrt(x * x + y * y + z * z));
+			}
+		});
+		// Minkovsky, general case, slow, maybe too slow to be useful
+		distanceFunctions.put(Integer.valueOf(6), new IDistanceFunc() {
+			@Override
+			public float execute(float x, float y, float z, float e) {
+				return (float)Math.pow(Math.pow(FastMath.abs(x), e) + Math.pow(FastMath.abs(y), e) + Math.pow(FastMath.abs(z), e), 1.0f / e);
+			}
+		});
+	}
+
+	protected static Map<Integer, IMusgraveFunction>	musgraveFunctions	= new HashMap<Integer, NoiseHelper.IMusgraveFunction>();
+	static {
+		musgraveFunctions.put(Integer.valueOf(TEX_MFRACTAL), new IMusgraveFunction() {
+			@Override
+			public float execute(Structure tex, float x, float y, float z) {
+				float mg_H = ((Number)tex.getFieldValue("mg_H")).floatValue();
+				float mg_lacunarity = ((Number)tex.getFieldValue("mg_lacunarity")).floatValue();
+				float mg_octaves = ((Number)tex.getFieldValue("mg_octaves")).floatValue();
+				int noisebasis = ((Number)tex.getFieldValue("noisebasis")).intValue();
+
+				float rmd, value = 1.0f, pwr = 1.0f, pwHL = (float)Math.pow(mg_lacunarity, -mg_H);
+				AbstractNoiseFunc abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noisebasis));
+				if(abstractNoiseFunc == null) {
+					abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));
+				}
+
+				for(int i = 0; i < (int)mg_octaves; ++i) {
+					value *= pwr * abstractNoiseFunc.executeS(x, y, z) + 1.0f;
+					pwr *= pwHL;
+					x *= mg_lacunarity;
+					y *= mg_lacunarity;
+					z *= mg_lacunarity;
+				}
+				rmd = (float)(mg_octaves - Math.floor(mg_octaves));
+				if(rmd != 0.0f) {
+					value *= rmd * abstractNoiseFunc.executeS(x, y, z) * pwr + 1.0f;
+				}
+				return value;
+			}
+		});
+		musgraveFunctions.put(Integer.valueOf(TEX_RIDGEDMF), new IMusgraveFunction() {
+			@Override
+			public float execute(Structure tex, float x, float y, float z) {
+				float mg_H = ((Number)tex.getFieldValue("mg_H")).floatValue();
+				float mg_lacunarity = ((Number)tex.getFieldValue("mg_lacunarity")).floatValue();
+				float mg_octaves = ((Number)tex.getFieldValue("mg_octaves")).floatValue();
+				float mg_offset = ((Number)tex.getFieldValue("mg_offset")).floatValue();
+				int noisebasis = ((Number)tex.getFieldValue("noisebasis")).intValue();
+				float mg_gain = ((Number)tex.getFieldValue("mg_gain")).floatValue();
+				float result, signal, weight;
+				float pwHL = (float)Math.pow(mg_lacunarity, -mg_H);
+				float pwr = pwHL; /* starts with i=1 instead of 0 */
+
+				AbstractNoiseFunc abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noisebasis));
+				if(abstractNoiseFunc == null) {
+					abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));
+				}
+
+				signal = mg_offset - FastMath.abs(abstractNoiseFunc.executeS(x, y, z));
+				signal *= signal;
+				result = signal;
+				weight = 1.0f;
+
+				for(int i = 1; i < (int)mg_octaves; ++i) {
+					x *= mg_lacunarity;
+					y *= mg_lacunarity;
+					z *= mg_lacunarity;
+					weight = signal * mg_gain;
+					if(weight > 1.0f) {
+						weight = 1.0f;
+					} else if(weight < 0.0) {
+						weight = 0.0f;
+					}
+					signal = mg_offset - FastMath.abs(abstractNoiseFunc.executeS(x, y, z));
+					signal *= signal;
+					signal *= weight;
+					result += signal * pwr;
+					pwr *= pwHL;
+				}
+				return result;
+			}
+		});
+		musgraveFunctions.put(Integer.valueOf(TEX_HYBRIDMF), new IMusgraveFunction() {
+			@Override
+			public float execute(Structure tex, float x, float y, float z) {
+				float mg_H = ((Number)tex.getFieldValue("mg_H")).floatValue();
+				float mg_lacunarity = ((Number)tex.getFieldValue("mg_lacunarity")).floatValue();
+				float mg_octaves = ((Number)tex.getFieldValue("mg_octaves")).floatValue();
+				float mg_offset = ((Number)tex.getFieldValue("mg_offset")).floatValue();
+				int noisebasis = ((Number)tex.getFieldValue("noisebasis")).intValue();
+				float mg_gain = ((Number)tex.getFieldValue("mg_gain")).floatValue();
+				float result, signal, weight, rmd;
+				float pwHL = (float)Math.pow(mg_lacunarity, -mg_H);
+				float pwr = pwHL; /* starts with i=1 instead of 0 */
+				AbstractNoiseFunc abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noisebasis));
+				if(abstractNoiseFunc == null) {
+					abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));
+				}
+
+				result = abstractNoiseFunc.executeS(x, y, z) + mg_offset;
+				weight = mg_gain * result;
+				x *= mg_lacunarity;
+				y *= mg_lacunarity;
+				z *= mg_lacunarity;
+
+				for(int i = 1; weight > 0.001f && i < (int)mg_octaves; ++i) {
+					if(weight > 1.0f) {
+						weight = 1.0f;
+					}
+					signal = (abstractNoiseFunc.executeS(x, y, z) + mg_offset) * pwr;
+					pwr *= pwHL;
+					result += weight * signal;
+					weight *= mg_gain * signal;
+					x *= mg_lacunarity;
+					y *= mg_lacunarity;
+					z *= mg_lacunarity;
+				}
+
+				rmd = mg_octaves - (float)Math.floor(mg_octaves);
+				if(rmd != 0.0f) {
+					result += rmd * (abstractNoiseFunc.executeS(x, y, z) + mg_offset) * pwr;
+				}
+				return result;
+			}
+		});
+		musgraveFunctions.put(Integer.valueOf(TEX_FBM), new IMusgraveFunction() {
+			@Override
+			public float execute(Structure tex, float x, float y, float z) {
+				float mg_H = ((Number)tex.getFieldValue("mg_H")).floatValue();
+				float mg_lacunarity = ((Number)tex.getFieldValue("mg_lacunarity")).floatValue();
+				float mg_octaves = ((Number)tex.getFieldValue("mg_octaves")).floatValue();
+				int noisebasis = ((Number)tex.getFieldValue("noisebasis")).intValue();
+				float rmd, value = 0.0f, pwr = 1.0f, pwHL = (float)Math.pow(mg_lacunarity, -mg_H);
+
+				AbstractNoiseFunc abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noisebasis));
+				if(abstractNoiseFunc == null) {
+					abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));
+				}
+
+				for(int i = 0; i < (int)mg_octaves; ++i) {
+					value += abstractNoiseFunc.executeS(x, y, z) * pwr;
+					pwr *= pwHL;
+					x *= mg_lacunarity;
+					y *= mg_lacunarity;
+					z *= mg_lacunarity;
+				}
+
+				rmd = (float)(mg_octaves - Math.floor(mg_octaves));
+				if(rmd != 0.f) {
+					value += rmd * abstractNoiseFunc.executeS(x, y, z) * pwr;
+				}
+				return value;
+			}
+		});
+		musgraveFunctions.put(Integer.valueOf(TEX_HTERRAIN), new IMusgraveFunction() {
+			@Override
+			public float execute(Structure tex, float x, float y, float z) {
+				float mg_H = ((Number)tex.getFieldValue("mg_H")).floatValue();
+				float mg_lacunarity = ((Number)tex.getFieldValue("mg_lacunarity")).floatValue();
+				float mg_octaves = ((Number)tex.getFieldValue("mg_octaves")).floatValue();
+				int noisebasis = ((Number)tex.getFieldValue("noisebasis")).intValue();
+				float mg_offset = ((Number)tex.getFieldValue("mg_offset")).floatValue();
+				float value, increment, rmd;
+				float pwHL = (float)Math.pow(mg_lacunarity, -mg_H);
+				float pwr = pwHL; /* starts with i=1 instead of 0 */
+				AbstractNoiseFunc abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noisebasis));
+				if(abstractNoiseFunc == null) {
+					abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(0));
+				}
+
+				/* first unscaled octave of function; later octaves are scaled */
+				value = mg_offset + abstractNoiseFunc.executeS(x, y, z);
+				x *= mg_lacunarity;
+				y *= mg_lacunarity;
+				z *= mg_lacunarity;
+
+				for(int i = 1; i < (int)mg_octaves; ++i) {
+					increment = (abstractNoiseFunc.executeS(x, y, z) + mg_offset) * pwr * value;
+					value += increment;
+					pwr *= pwHL;
+					x *= mg_lacunarity;
+					y *= mg_lacunarity;
+					z *= mg_lacunarity;
+				}
+
+				rmd = mg_octaves - (float)Math.floor(mg_octaves);
+				if(rmd != 0.0) {
+					increment = (abstractNoiseFunc.executeS(x, y, z) + mg_offset) * pwr * value;
+					value += rmd * increment;
+				}
+				return value;
+			}
+		});
+	}
+
+	/**
+	 * THE FOLLOWING METHODS HELP IN COMPUTATION OF THE TEXTURES.
+	 */
+	protected void brightnesAndContrast(TexResult texres, float contrast, float brightness) {
+		texres.tin = (texres.tin - 0.5f) * contrast + brightness - 0.5f;
+		if(texres.tin < 0.0f) {
+			texres.tin = 0.0f;
+		} else if(texres.tin > 1.0f) {
+			texres.tin = 1.0f;
+		}
+	}
+
+	protected void brightnesAndContrastRGB(Structure tex, TexResult texres) {
+		float contrast = ((Number)tex.getFieldValue("contrast")).floatValue();
+		float bright = ((Number)tex.getFieldValue("bright")).floatValue();
+		float rfac = ((Number)tex.getFieldValue("rfac")).floatValue();
+		float gfac = ((Number)tex.getFieldValue("gfac")).floatValue();
+		float bfac = ((Number)tex.getFieldValue("bfac")).floatValue();
+
+		texres.tr = rfac * ((texres.tr - 0.5f) * contrast + bright - 0.5f);
+		if(texres.tr < 0.0f) {
+			texres.tr = 0.0f;
+		}
+		texres.tg = gfac * ((texres.tg - 0.5f) * contrast + bright - 0.5f);
+		if(texres.tg < 0.0f) {
+			texres.tg = 0.0f;
+		}
+		texres.tb = bfac * ((texres.tb - 0.5f) * contrast + bright - 0.5f);
+		if(texres.tb < 0.0f) {
+			texres.tb = 0.0f;
+		}
+	}
+
+	/* this allows colorbanded textures to control normals as well */
+	public void texNormalDerivate(ColorBand colorBand, TexResult texres, DataRepository dataRepository) {
+		if(texres.nor != null) {
+			TexResult fakeTexresult;
+			try {
+				fakeTexresult = (TexResult)texres.clone();
+			} catch(CloneNotSupportedException e) {
+				throw new IllegalStateException("Texture result class MUST support cloning!", e);
+			}
+
+			float fac0 = fakeTexresult.tr + fakeTexresult.tg + fakeTexresult.tb;
+			fakeTexresult.tin = texres.nor[0];
+			this.doColorband(colorBand, fakeTexresult, dataRepository);
+
+			float fac1 = fakeTexresult.tr + fakeTexresult.tg + fakeTexresult.tb;
+			fakeTexresult.tin = texres.nor[1];
+			this.doColorband(colorBand, fakeTexresult, dataRepository);
+
+			float fac2 = fakeTexresult.tr + fakeTexresult.tg + fakeTexresult.tb;
+			fakeTexresult.tin = texres.nor[2];
+			this.doColorband(colorBand, fakeTexresult, dataRepository);
+
+			float fac3 = fakeTexresult.tr + fakeTexresult.tg + fakeTexresult.tb;
+
+			texres.nor[0] = 0.3333f * (fac0 - fac1);
+			texres.nor[1] = 0.3333f * (fac0 - fac2);
+			texres.nor[2] = 0.3333f * (fac0 - fac3);
+
+			texres.nor[0] = texres.tin - texres.nor[0];
+			texres.nor[1] = texres.tin - texres.nor[1];
+			texres.nor[2] = texres.tin - texres.nor[2];
+		}
+	}
+
+	/**
+	 * This method calculates the colorband for the texture.
+	 * @param colorBand
+	 *        the colorband data
+	 * @param texres
+	 *        the texture pixel result
+	 * @param dataRepository
+	 *        the data repository
+	 * @return <b>true</b> if calculation suceedess and <b>false</b> otherwise
+	 */
+	public boolean doColorband(ColorBand colorBand, TexResult texres, DataRepository dataRepository) {
+		CBData cbd1, cbd2, cbd0, cbd3;
+		int i1 = 0, i2 = 0, a;
+		float fac, mfac;
+		float[] t = new float[4];
+
+		if(colorBand == null || colorBand.tot == 0) {
+			return true;
+		}
+
+		cbd1 = colorBand.data[0];
+		if(colorBand.tot == 1) {
+			texres.tr = cbd1.r;
+			texres.tg = cbd1.g;
+			texres.tb = cbd1.b;
+			texres.ta = cbd1.a;
+		} else {
+			if(texres.tin <= cbd1.pos && colorBand.ipotype < 2) {
+				texres.tr = cbd1.r;
+				texres.tg = cbd1.g;
+				texres.tb = cbd1.b;
+				texres.ta = cbd1.a;
+			} else {
+				/* we're looking for first pos > in */
+				for(a = 0; a < colorBand.tot; ++a, ++i1) {
+					cbd1 = colorBand.data[i1];
+					if(cbd1.pos > texres.tin) {
+						break;
+					}
+				}
+
+				if(a == colorBand.tot) {
+					cbd2 = colorBand.data[i1 - 1];
+					try {
+						cbd1 = (CBData)cbd2.clone();
+					} catch(CloneNotSupportedException e) {
+						throw new IllegalStateException("Clone not supported for " + CBData.class.getName() + " class! Fix that!");
+					}
+					cbd1.pos = 1.0f;
+				} else if(a == 0) {
+					try {
+						cbd2 = (CBData)cbd1.clone();
+					} catch(CloneNotSupportedException e) {
+						throw new IllegalStateException("Clone not supported for " + CBData.class.getName() + " class! Fix that!");
+					}
+					cbd2.pos = 0.0f;
+				} else {
+					cbd2 = colorBand.data[i1 - 1];
+				}
+
+				if(texres.tin >= cbd1.pos && colorBand.ipotype < 2) {
+					texres.tr = cbd1.r;
+					texres.tg = cbd1.g;
+					texres.tb = cbd1.b;
+					texres.ta = cbd1.a;
+				} else {
+
+					if(cbd2.pos != cbd1.pos) {
+						fac = (texres.tin - cbd1.pos) / (cbd2.pos - cbd1.pos);
+					} else {
+						fac = 0.0f;
+					}
+
+					if(colorBand.ipotype == 4) {
+						/* constant */
+						texres.tr = cbd2.r;
+						texres.tg = cbd2.g;
+						texres.tb = cbd2.b;
+						texres.ta = cbd2.a;
+						return true;
+					}
+
+					if(colorBand.ipotype >= 2) {
+						/* ipo from right to left: 3 2 1 0 */
+
+						if(a >= colorBand.tot - 1) {
+							cbd0 = cbd1;
+						} else {
+							cbd0 = colorBand.data[i1 + 1];
+						}
+						if(a < 2) {
+							cbd3 = cbd2;
+						} else {
+							cbd3 = colorBand.data[i2 - 1];
+						}
+
+						fac = FastMath.clamp(fac, 0.0f, 1.0f);
+
+						if(colorBand.ipotype == 3) {
+							this.setFourIpo(fac, t, KEY_CARDINAL);
+						} else {
+							this.setFourIpo(fac, t, KEY_BSPLINE);
+						}
+
+						texres.tr = t[3] * cbd3.r + t[2] * cbd2.r + t[1] * cbd1.r + t[0] * cbd0.r;
+						texres.tg = t[3] * cbd3.g + t[2] * cbd2.g + t[1] * cbd1.g + t[0] * cbd0.g;
+						texres.tb = t[3] * cbd3.b + t[2] * cbd2.b + t[1] * cbd1.b + t[0] * cbd0.b;
+						texres.ta = t[3] * cbd3.a + t[2] * cbd2.a + t[1] * cbd1.a + t[0] * cbd0.a;
+						texres.tr = FastMath.clamp(texres.tr, 0.0f, 1.0f);
+						texres.tg = FastMath.clamp(texres.tg, 0.0f, 1.0f);
+						texres.tb = FastMath.clamp(texres.tb, 0.0f, 1.0f);
+						texres.ta = FastMath.clamp(texres.ta, 0.0f, 1.0f);
+					} else {
+
+						if(colorBand.ipotype == 1) { /* EASE */
+							mfac = fac * fac;
+							fac = 3.0f * mfac - 2.0f * mfac * fac;
+						}
+						mfac = 1.0f - fac;
+
+						texres.tr = mfac * cbd1.r + fac * cbd2.r;
+						texres.tg = mfac * cbd1.g + fac * cbd2.g;
+						texres.tb = mfac * cbd1.b + fac * cbd2.b;
+						texres.ta = mfac * cbd1.a + fac * cbd2.a;
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	protected void setFourIpo(float d, float[] data, int type) {
+		if(type == KEY_LINEAR) {
+			data[0] = 0.0f;
+			data[1] = 1.0f - d;
+			data[2] = d;
+			data[3] = 0.0f;
+		} else {
+			float d2 = d * d;
+			float d3 = d2 * d;
+			if(type == KEY_CARDINAL) {
+				float fc = 0.71f;
+				data[0] = -fc * d3 + 2.0f * fc * d2 - fc * d;
+				data[1] = (2.0f - fc) * d3 + (fc - 3.0f) * d2 + 1.0f;
+				data[2] = (fc - 2.0f) * d3 + (3.0f - 2.0f * fc) * d2 + fc * d;
+				data[3] = fc * d3 - fc * d2;
+			} else if(type == KEY_BSPLINE) {
+				data[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f;
+				data[1] = 0.5f * d3 - d2 + 0.6666666f;
+				data[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f;
+				data[3] = 0.16666666f * d3;
+			}
+		}
+	}
+
+	interface IWaveForm {
+		float execute(float x);
+	}
+
+	protected static IWaveForm[]	waveformFunctions	= new IWaveForm[3];
+	static {
+		waveformFunctions[0] = new IWaveForm() {// tex_sin
+			@Override
+			public float execute(float x) {
+				return 0.5f + 0.5f * (float)Math.sin(x);
+			}
+		};
+		waveformFunctions[1] = new IWaveForm() {// tex_saw
+			@Override
+			public float execute(float x) {
+				int n = (int)(x / FastMath.TWO_PI);
+				x -= n * FastMath.TWO_PI;
+				if(x < 0.0f) {
+					x += FastMath.TWO_PI;
+				}
+				return x / FastMath.TWO_PI;
+			}
+		};
+		waveformFunctions[2] = new IWaveForm() {// tex_tri
+			@Override
+			public float execute(float x) {
+				return 1.0f - 2.0f * FastMath.abs((float)Math.floor(x * 1.0f / FastMath.TWO_PI + 0.5f) - x * 1.0f / FastMath.TWO_PI);
+			}
+		};
+	}
+
+	/* computes basic wood intensity value at x,y,z */
+	public float woodInt(Structure tex, float x, float y, float z, DataRepository dataRepository) {
+		int noisebasis2 = ((Number)tex.getFieldValue("noisebasis2")).intValue();
+		int noisebasis = ((Number)tex.getFieldValue("noisebasis")).intValue();
+		int stype = ((Number)tex.getFieldValue("stype")).intValue();
+		float noisesize = ((Number)tex.getFieldValue("noisesize")).floatValue();
+		float turbul = ((Number)tex.getFieldValue("turbul")).floatValue();
+		int noiseType = ((Number)tex.getFieldValue("noisetype")).intValue();
+		float wi = 0;
+		int waveform = noisebasis2; /* wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2 */
+		int wt = stype; /* wood type: TEX_BAND=0, TEX_RING=1, TEX_BANDNOISE=2, TEX_RINGNOISE=3 */
+
+		if(waveform > TEX_TRI || waveform < TEX_SIN) {
+			waveform = 0; /* check to be sure noisebasis2 is initialized ahead of time */
+		}
+
+		if(wt == TEX_BAND) {
+			wi = waveformFunctions[waveform].execute((x + y + z) * 10.0f);
+		} else if(wt == TEX_RING) {
+			wi = waveformFunctions[waveform].execute((float)Math.sqrt(x * x + y * y + z * z) * 20.0f);
+		} else if(wt == TEX_BANDNOISE) {
+			wi = turbul * this.bliGNoise(noisesize, x, y, z, noiseType != TEX_NOISESOFT, noisebasis);
+			wi = waveformFunctions[waveform].execute((x + y + z) * 10.0f + wi);
+		} else if(wt == TEX_RINGNOISE) {
+			wi = turbul * this.bliGNoise(noisesize, x, y, z, noiseType != TEX_NOISESOFT, noisebasis);
+			wi = waveformFunctions[waveform].execute((float)Math.sqrt(x * x + y * y + z * z) * 20.0f + wi);
+		}
+		return wi;
+	}
+
+	/* computes basic marble intensity at x,y,z */
+	public float marbleInt(Structure tex, float x, float y, float z, DataRepository dataRepository) {
+		float noisesize = ((Number)tex.getFieldValue("noisesize")).floatValue();
+		int noisebasis = ((Number)tex.getFieldValue("noisebasis")).intValue();
+		int noisedepth = ((Number)tex.getFieldValue("noisedepth")).intValue();
+		int stype = ((Number)tex.getFieldValue("stype")).intValue();/* marble type: TEX_SOFT=0, TEX_SHARP=1,TEX_SHAPER=2 */
+		float turbul = ((Number)tex.getFieldValue("turbul")).floatValue();
+		int noisetype = ((Number)tex.getFieldValue("noisetype")).intValue();
+		int waveform = ((Number)tex.getFieldValue("noisebasis2")).intValue(); /* wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2 */
+
+		if(waveform > TEX_TRI || waveform < TEX_SIN) {
+			waveform = 0; /* check to be sure noisebasis2 isn't initialized ahead of time */
+		}
+
+		float n = 5.0f * (x + y + z);
+		float mi = n + turbul * this.bliGTurbulence(noisesize, x, y, z, noisedepth, noisetype != TEX_NOISESOFT, noisebasis);
+
+		if(stype >= NoiseHelper.TEX_SOFT) { /* TEX_SOFT always true */
+			mi = waveformFunctions[waveform].execute(mi);
+			if(stype == TEX_SHARP) {
+				mi = (float)Math.sqrt(mi);
+			} else if(stype == TEX_SHARPER) {
+				mi = (float)Math.sqrt(Math.sqrt(mi));
+			}
+		}
+		return mi;
+	}
+
+	public void voronoi(float x, float y, float z, float[] da, float[] pa, float me, int dtype) {
+		AbstractNoiseFunc.voronoi(x, y, z, da, pa, me, dtype);
+	}
+
+	public void cellNoiseV(float x, float y, float z, float[] ca) {
+		AbstractNoiseFunc.cellNoiseV(x, y, z, ca);
+	}
+
+	/**
+	 * THE FOLLOWING METHODS HELP IN NOISE COMPUTATIONS
+	 */
+
+	/**
+	 * Separated from orgBlenderNoise above, with scaling.
+	 * @param noisesize
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @return
+	 */
+	private float bliHnoise(float noisesize, float x, float y, float z) {
+		if(noisesize == 0.0) {
+			return 0.0f;
+		}
+		x = (1.0f + x) / noisesize;
+		y = (1.0f + y) / noisesize;
+		z = (1.0f + z) / noisesize;
+		return noiseFunctions.get(0).execute(x, y, z);
+	}
+
+	/**
+	 * @param noisesize
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @param nr
+	 * @return
+	 */
+	public float bliTurbulence(float noisesize, float x, float y, float z, int nr) {
+		float d = 0.5f, div = 1.0f;
+
+		float s = this.bliHnoise(noisesize, x, y, z);
+		while(nr > 0) {
+			s += d * this.bliHnoise(noisesize * d, x, y, z);
+			div += d;
+			d *= 0.5;
+			--nr;
+		}
+		return s / div;
+	}
+
+	/**
+	 * @param noisesize
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @param nr
+	 * @return
+	 */
+	public float bliTurbulence1(float noisesize, float x, float y, float z, int nr) {
+		float s, d = 0.5f, div = 1.0f;
+
+		s = FastMath.abs((-1.0f + 2.0f * this.bliHnoise(noisesize, x, y, z)));
+		while(nr > 0) {
+			s += Math.abs(d * (-1.0f + 2.0f * this.bliHnoise(noisesize * d, x, y, z)));
+			div += d;
+			d *= 0.5;
+			--nr;
+		}
+		return s / div;
+	}
+
+	/**
+	 * @param noisesize
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @return
+	 */
+	public float bliHnoisep(float noisesize, float x, float y, float z) {
+		return noiseFunctions.get(Integer.valueOf(0)).noise3Perlin(new float[] {x / noisesize, y / noisesize, z / noisesize});
+	}
+
+	/**
+	 * @param point
+	 * @param lofreq
+	 * @param hifreq
+	 * @return
+	 */
+	public float turbulencePerlin(float[] point, float lofreq, float hifreq) {
+		float freq, t = 0, p[] = new float[] {point[0] + 123.456f, point[1], point[2]};
+		for(freq = lofreq; freq < hifreq; freq *= 2.) {
+			t += Math.abs(noiseFunctions.get(Integer.valueOf(0)).noise3Perlin(p)) / freq;
+			p[0] *= 2.0f;
+			p[1] *= 2.0f;
+			p[2] *= 2.0f;
+		}
+		return t - 0.3f; /* readjust to make mean value = 0.0 */
+	}
+
+	/**
+	 * @param noisesize
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @param nr
+	 * @return
+	 */
+	public float turbulencep(float noisesize, float x, float y, float z, int nr) {
+		float[] vec = new float[] {x / noisesize, y / noisesize, z / noisesize};
+		++nr;
+		return this.turbulencePerlin(vec, 1.0f, (1 << nr));
+	}
+
+	/**
+	 * Newnoise: generic noise function for use with different noisebases
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @param oct
+	 * @param isHard
+	 * @param noisebasis
+	 * @return
+	 */
+	public float bliGNoise(float noisesize, float x, float y, float z, boolean isHard, int noisebasis) {
+		AbstractNoiseFunc abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noisebasis));
+		if(abstractNoiseFunc == null) {
+			abstractNoiseFunc = noiseFunctions.get(0);
+			noisebasis = 0;
+		}
+		if(noisebasis == 0) {// add one to make return value same as BLI_hnoise
+			x += 1;
+			y += 1;
+			z += 1;
+		}
+
+		if(noisesize != 0.0) {
+			noisesize = 1.0f / noisesize;
+			x *= noisesize;
+			y *= noisesize;
+			z *= noisesize;
+		}
+		if(isHard) {
+			return Math.abs(2.0f * abstractNoiseFunc.execute(x, y, z) - 1.0f);
+		}
+		return abstractNoiseFunc.execute(x, y, z);
+	}
+
+	/**
+	 * Newnoise: generic turbulence function for use with different noisebasis
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @param oct
+	 * @param isHard
+	 * @param noisebasis
+	 * @return
+	 */
+	public float bliGTurbulence(float noisesize, float x, float y, float z, int oct, boolean isHard, int noisebasis) {
+		AbstractNoiseFunc abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noisebasis));
+		if(abstractNoiseFunc == null) {
+			abstractNoiseFunc = noiseFunctions.get(0);
+			noisebasis = 0;
+		}
+		if(noisebasis == 0) {// add one to make return value same as BLI_hnoise
+			x += 1;
+			y += 1;
+			z += 1;
+		}
+		float sum = 0, t, amp = 1, fscale = 1;
+
+		if(noisesize != 0.0) {
+			noisesize = 1.0f / noisesize;
+			x *= noisesize;
+			y *= noisesize;
+			z *= noisesize;
+		}
+		for(int i = 0; i <= oct; ++i, amp *= 0.5, fscale *= 2) {
+			t = abstractNoiseFunc.execute(fscale * x, fscale * y, fscale * z);
+			if(isHard) {
+				t = FastMath.abs(2.0f * t - 1.0f);
+			}
+			sum += t * amp;
+		}
+
+		sum *= (float)(1 << oct) / (float)((1 << oct + 1) - 1);
+		return sum;
+	}
+
+	/**
+	 * "Variable Lacunarity Noise" A distorted variety of Perlin noise. This method is used to calculate distorted noise
+	 * texture.
+	 * @param x
+	 * @param y
+	 * @param z
+	 * @param distortion
+	 * @param nbas1
+	 * @param nbas2
+	 * @return
+	 */
+	public float mgVLNoise(float x, float y, float z, float distortion, int nbas1, int nbas2) {
+		AbstractNoiseFunc abstractNoiseFunc1 = noiseFunctions.get(Integer.valueOf(nbas1));
+		if(abstractNoiseFunc1 == null) {
+			abstractNoiseFunc1 = noiseFunctions.get(Integer.valueOf(0));
+		}
+		AbstractNoiseFunc abstractNoiseFunc2 = noiseFunctions.get(Integer.valueOf(nbas2));
+		if(abstractNoiseFunc2 == null) {
+			abstractNoiseFunc2 = noiseFunctions.get(Integer.valueOf(0));
+		}
+		// get a random vector and scale the randomization
+		float rx = abstractNoiseFunc1.execute(x + 13.5f, y + 13.5f, z + 13.5f) * distortion;
+		float ry = abstractNoiseFunc1.execute(x, y, z) * distortion;
+		float rz = abstractNoiseFunc1.execute(x - 13.5f, y - 13.5f, z - 13.5f) * distortion;
+		return abstractNoiseFunc2.executeS(x + rx, y + ry, z + rz); //distorted-domain noise
+	}
+
+	public void mgMFractalOrfBmTex(Structure tex, float[] texvec, ColorBand colorBand, TexResult texres, DataRepository dataRepository) {
+		int stype = ((Number)tex.getFieldValue("stype")).intValue();
+		float nsOutscale = ((Number)tex.getFieldValue("ns_outscale")).floatValue();
+		float nabla = ((Number)tex.getFieldValue("nabla")).floatValue();
+		float noisesize = ((Number)tex.getFieldValue("noisesize")).floatValue();
+		float contrast = ((Number)tex.getFieldValue("contrast")).floatValue();
+		float brightness = ((Number)tex.getFieldValue("bright")).floatValue();
+
+		IMusgraveFunction mgravefunc = stype == TEX_MFRACTAL ? musgraveFunctions.get(Integer.valueOf(stype)) : musgraveFunctions.get(Integer.valueOf(TEX_FBM));
+
+		texres.tin = nsOutscale * mgravefunc.execute(tex, texvec[0], texvec[1], texvec[2]);
+		if(texres.nor != null) {
+			float offs = nabla / noisesize; // also scaling of texvec
+			// calculate bumpnormal
+			texres.nor[0] = nsOutscale * mgravefunc.execute(tex, texvec[0] + offs, texvec[1], texvec[2]);
+			texres.nor[1] = nsOutscale * mgravefunc.execute(tex, texvec[0], texvec[1] + offs, texvec[2]);
+			texres.nor[2] = nsOutscale * mgravefunc.execute(tex, texvec[0], texvec[1], texvec[2] + offs);
+			this.texNormalDerivate(colorBand, texres, dataRepository);
+		}
+		this.brightnesAndContrast(texres, contrast, brightness);
+	}
+
+	public void mgRidgedOrHybridMFTex(Structure tex, float[] texvec, ColorBand colorBand, TexResult texres, DataRepository dataRepository) {
+		int stype = ((Number)tex.getFieldValue("stype")).intValue();
+		float nsOutscale = ((Number)tex.getFieldValue("ns_outscale")).floatValue();
+		float nabla = ((Number)tex.getFieldValue("nabla")).floatValue();
+		float noisesize = ((Number)tex.getFieldValue("noisesize")).floatValue();
+		float contrast = ((Number)tex.getFieldValue("contrast")).floatValue();
+		float brightness = ((Number)tex.getFieldValue("bright")).floatValue();
+
+		IMusgraveFunction mgravefunc = stype == TEX_RIDGEDMF ? musgraveFunctions.get(Integer.valueOf(stype)) : musgraveFunctions.get(Integer.valueOf(TEX_HYBRIDMF));
+
+		texres.tin = nsOutscale * mgravefunc.execute(tex, texvec[0], texvec[1], texvec[2]);
+		if(texres.nor != null) {
+			float offs = nabla / noisesize; // also scaling of texvec
+			// calculate bumpnormal
+			texres.nor[0] = nsOutscale * mgravefunc.execute(tex, texvec[0] + offs, texvec[1], texvec[2]);
+			texres.nor[1] = nsOutscale * mgravefunc.execute(tex, texvec[0], texvec[1] + offs, texvec[2]);
+			texres.nor[2] = nsOutscale * mgravefunc.execute(tex, texvec[0], texvec[1], texvec[2] + offs);
+			this.texNormalDerivate(colorBand, texres, dataRepository);
+		}
+		this.brightnesAndContrast(texres, contrast, brightness);
+	}
+
+	public void mgHTerrainTex(Structure tex, float[] texvec, ColorBand colorBand, TexResult texres, DataRepository dataRepository) {
+		float nsOutscale = ((Number)tex.getFieldValue("ns_outscale")).floatValue();
+		float nabla = ((Number)tex.getFieldValue("nabla")).floatValue();
+		float noisesize = ((Number)tex.getFieldValue("noisesize")).floatValue();
+		float contrast = ((Number)tex.getFieldValue("contrast")).floatValue();
+		float brightness = ((Number)tex.getFieldValue("bright")).floatValue();
+
+		IMusgraveFunction musgraveFunction = musgraveFunctions.get(Integer.valueOf(TEX_HTERRAIN));
+		texres.tin = nsOutscale * musgraveFunction.execute(tex, texvec[0], texvec[1], texvec[2]);
+		if(texres.nor != null) {
+			float offs = nabla / noisesize; // also scaling of texvec
+			// calculate bumpnormal
+			texres.nor[0] = nsOutscale * musgraveFunction.execute(tex, texvec[0] + offs, texvec[1], texvec[2]);
+			texres.nor[1] = nsOutscale * musgraveFunction.execute(tex, texvec[0], texvec[1] + offs, texvec[2]);
+			texres.nor[2] = nsOutscale * musgraveFunction.execute(tex, texvec[0], texvec[1], texvec[2] + offs);
+			this.texNormalDerivate(colorBand, texres, dataRepository);
+		}
+		this.brightnesAndContrast(texres, contrast, brightness);
+	}
+
+	/**
+	 * This class is abstract to the noise functions computations. It has two methods. One calculates the Signed (with
+	 * 'S' at the end) and the other Unsigned value.
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	protected static abstract class AbstractNoiseFunc {
+		/**
+		 * This method calculates the unsigned value of the noise.
+		 * @param x
+		 *        the x texture coordinate
+		 * @param y
+		 *        the y texture coordinate
+		 * @param z
+		 *        the z texture coordinate
+		 * @return value of the noise
+		 */
+		public abstract float execute(float x, float y, float z);
+
+		/**
+		 * This method calculates the signed value of the noise.
+		 * @param x
+		 *        the x texture coordinate
+		 * @param y
+		 *        the y texture coordinate
+		 * @param z
+		 *        the z texture coordinate
+		 * @return value of the noise
+		 */
+		public abstract float executeS(float x, float y, float z);
+
+		/*
+		 * Not 'pure' Worley, but the results are virtually the same. Returns distances in da and point coords in pa
+		 */
+		protected static void voronoi(float x, float y, float z, float[] da, float[] pa, float me, int dtype) {
+			float xd, yd, zd, d, p[];
+
+			IDistanceFunc distanceFunc = distanceFunctions.get(Integer.valueOf(dtype));
+			if(distanceFunc == null) {
+				distanceFunc = distanceFunctions.get(Integer.valueOf(0));
+			}
+
+			int xi = (int)FastMath.floor(x);
+			int yi = (int)FastMath.floor(y);
+			int zi = (int)FastMath.floor(z);
+			da[0] = da[1] = da[2] = da[3] = 1e10f;
+			for(int xx = xi - 1; xx <= xi + 1; ++xx) {
+				for(int yy = yi - 1; yy <= yi + 1; ++yy) {
+					for(int zz = zi - 1; zz <= zi + 1; ++zz) {
+						p = AbstractNoiseFunc.hashPoint(xx, yy, zz);
+						xd = x - (p[0] + xx);
+						yd = y - (p[1] + yy);
+						zd = z - (p[2] + zz);
+						d = distanceFunc.execute(xd, yd, zd, me);
+						if(d < da[0]) {
+							da[3] = da[2];
+							da[2] = da[1];
+							da[1] = da[0];
+							da[0] = d;
+							pa[9] = pa[6];
+							pa[10] = pa[7];
+							pa[11] = pa[8];
+							pa[6] = pa[3];
+							pa[7] = pa[4];
+							pa[8] = pa[5];
+							pa[3] = pa[0];
+							pa[4] = pa[1];
+							pa[5] = pa[2];
+							pa[0] = p[0] + xx;
+							pa[1] = p[1] + yy;
+							pa[2] = p[2] + zz;
+						} else if(d < da[1]) {
+							da[3] = da[2];
+							da[2] = da[1];
+							da[1] = d;
+							pa[9] = pa[6];
+							pa[10] = pa[7];
+							pa[11] = pa[8];
+							pa[6] = pa[3];
+							pa[7] = pa[4];
+							pa[8] = pa[5];
+							pa[3] = p[0] + xx;
+							pa[4] = p[1] + yy;
+							pa[5] = p[2] + zz;
+						} else if(d < da[2]) {
+							da[3] = da[2];
+							da[2] = d;
+							pa[9] = pa[6];
+							pa[10] = pa[7];
+							pa[11] = pa[8];
+							pa[6] = p[0] + xx;
+							pa[7] = p[1] + yy;
+							pa[8] = p[2] + zz;
+						} else if(d < da[3]) {
+							da[3] = d;
+							pa[9] = p[0] + xx;
+							pa[10] = p[1] + yy;
+							pa[11] = p[2] + zz;
+						}
+					}
+				}
+			}
+		}
+
+		// #define HASHVEC(x,y,z) hashvectf+3*hash[ (hash[ (hash[(z) & 255]+(y)) & 255]+(x)) & 255]
+
+		/* needed for voronoi */
+		// #define HASHPNT(x,y,z) hashpntf+3*hash[ (hash[ (hash[(z) & 255]+(y)) & 255]+(x)) & 255]
+		protected static float[] hashPoint(int x, int y, int z) {
+			float[] result = new float[3];
+			result[0] = hashpntf[3 * hash[hash[hash[z & 255] + y & 255] + x & 255]];
+			result[1] = hashpntf[3 * hash[hash[hash[z & 255] + y & 255] + x & 255] + 1];
+			result[2] = hashpntf[3 * hash[hash[hash[z & 255] + y & 255] + x & 255] + 2];
+			return result;
+		}
+
+		// #define setup(i,b0,b1,r0,r1) \
+		// t = vec[i] + 10000.; \
+		// b0 = ((int)t) & 255; \
+		// b1 = (b0+1) & 255; \
+		// r0 = t - (int)t; \
+		// r1 = r0 - 1.;
+
+		// vec[3]
+		public float noise3Perlin(float[] vec) {
+			int bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11;
+			float rx0, rx1, ry0, ry1, rz0, rz1, sx, sy, sz, a, b, c, d, t, u, v;
+			int i, j;
+
+			// setup(0, bx0,bx1, rx0,rx1);
+			t = vec[0] + 10000.0f;
+			bx0 = (int)t & 255;
+			bx1 = bx0 + 1 & 255;
+			rx0 = t - (int)t;
+			rx1 = rx0 - 1.0f;
+			// setup(1, by0,by1, ry0,ry1);
+			t = vec[0] + 10000.0f;
+			by0 = (int)t & 255;
+			by1 = by0 + 1 & 255;
+			ry0 = t - (int)t;
+			ry1 = ry0 - 1.0f;
+			// setup(2, bz0,bz1, rz0,rz1);
+			t = vec[0] + 10000.0f;
+			bz0 = (int)t & 255;
+			bz1 = bz0 + 1 & 255;
+			rz0 = t - (int)t;
+			rz1 = rz0 - 1.0f;
+
+			i = p[bx0];
+			j = p[bx1];
+
+			b00 = p[i + by0];
+			b10 = p[j + by0];
+			b01 = p[i + by1];
+			b11 = p[j + by1];
+
+			/* lerp moved to improved perlin above */
+
+			sx = this.surve(rx0);
+			sy = this.surve(ry0);
+			sz = this.surve(rz0);
+
+			float[] q = new float[3];
+			q = g[b00 + bz0];
+			u = this.at(rx0, ry0, rz0, q);
+			q = g[b10 + bz0];
+			v = this.at(rx1, ry0, rz0, q);
+			a = this.lerp(sx, u, v);
+
+			q = g[b01 + bz0];
+			u = this.at(rx0, ry1, rz0, q);
+			q = g[b11 + bz0];
+			v = this.at(rx1, ry1, rz0, q);
+			b = this.lerp(sx, u, v);
+
+			c = this.lerp(sy, a, b); /* interpolate in y at lo x */
+
+			q = g[b00 + bz1];
+			u = this.at(rx0, ry0, rz1, q);
+			q = g[b10 + bz1];
+			v = this.at(rx1, ry0, rz1, q);
+			a = this.lerp(sx, u, v);
+
+			q = g[b01 + bz1];
+			u = this.at(rx0, ry1, rz1, q);
+			q = g[b11 + bz1];
+			v = this.at(rx1, ry1, rz1, q);
+			b = this.lerp(sx, u, v);
+
+			d = this.lerp(sy, a, b); /* interpolate in y at hi x */
+
+			return 1.5f * this.lerp(sz, c, d); /* interpolate in z */
+		}
+
+		public float orgBlenderNoise(float x, float y, float z) {
+			float cn1, cn2, cn3, cn4, cn5, cn6, i;
+			float ox, oy, oz, jx, jy, jz;
+			float n = 0.5f;
+			int ix, iy, iz, b00, b01, b10, b11, b20, b21;
+
+			ox = x - (ix = (int)Math.floor(x));
+			oy = y - (iy = (int)Math.floor(y));
+			oz = z - (iz = (int)Math.floor(z));
+
+			jx = ox - 1;
+			jy = oy - 1;
+			jz = oz - 1;
+
+			cn1 = ox * ox;
+			cn2 = oy * oy;
+			cn3 = oz * oz;
+			cn4 = jx * jx;
+			cn5 = jy * jy;
+			cn6 = jz * jz;
+
+			cn1 = 1.0f - 3.0f * cn1 + 2.0f * cn1 * ox;
+			cn2 = 1.0f - 3.0f * cn2 + 2.0f * cn2 * oy;
+			cn3 = 1.0f - 3.0f * cn3 + 2.0f * cn3 * oz;
+			cn4 = 1.0f - 3.0f * cn4 - 2.0f * cn4 * jx;
+			cn5 = 1.0f - 3.0f * cn5 - 2.0f * cn5 * jy;
+			cn6 = 1.0f - 3.0f * cn6 - 2.0f * cn6 * jz;
+
+			b00 = hash[hash[ix & 255] + (iy & 255)];
+			b10 = hash[hash[ix + 1 & 255] + (iy & 255)];
+			b01 = hash[hash[ix & 255] + (iy + 1 & 255)];
+			b11 = hash[hash[ix + 1 & 255] + (iy + 1 & 255)];
+
+			b20 = iz & 255;
+			b21 = iz + 1 & 255;
+
+			/* 0 */
+			i = cn1 * cn2 * cn3;
+			int hIndex = 3 * hash[b20 + b00];
+			n += i * (hashvectf[hIndex] * ox + hashvectf[hIndex + 1] * oy + hashvectf[hIndex + 2] * oz);
+			/* 1 */
+			i = cn1 * cn2 * cn6;
+			hIndex = 3 * hash[b21 + b00];
+			n += i * (hashvectf[hIndex] * ox + hashvectf[hIndex + 1] * oy + hashvectf[hIndex + 2] * jz);
+			/* 2 */
+			i = cn1 * cn5 * cn3;
+			hIndex = 3 * hash[b20 + b01];
+			n += i * (hashvectf[hIndex] * ox + hashvectf[hIndex + 1] * jy + hashvectf[hIndex + 2] * oz);
+			/* 3 */
+			i = cn1 * cn5 * cn6;
+			hIndex = 3 * hash[b21 + b01];
+			n += i * (hashvectf[hIndex] * ox + hashvectf[hIndex + 1] * jy + hashvectf[hIndex + 2] * jz);
+			/* 4 */
+			i = cn4 * cn2 * cn3;
+			hIndex = 3 * hash[b20 + b10];
+			n += i * (hashvectf[hIndex] * jx + hashvectf[hIndex + 1] * oy + hashvectf[hIndex + 2] * oz);
+			/* 5 */
+			i = cn4 * cn2 * cn6;
+			hIndex = 3 * hash[b21 + b10];
+			n += i * (hashvectf[hIndex] * jx + hashvectf[hIndex + 1] * oy + hashvectf[hIndex + 2] * jz);
+			/* 6 */
+			i = cn4 * cn5 * cn3;
+			hIndex = 3 * hash[b20 + b11];
+			n += i * (hashvectf[hIndex] * jx + hashvectf[hIndex + 1] * jy + hashvectf[hIndex + 2] * oz);
+			/* 7 */
+			i = cn4 * cn5 * cn6;
+			hIndex = 3 * hash[b21 + b11];
+			n += i * (hashvectf[hIndex] * jx + hashvectf[hIndex + 1] * jy + hashvectf[hIndex + 2] * jz);
+
+			if(n < 0.0f) {
+				n = 0.0f;
+			} else if(n > 1.0f) {
+				n = 1.0f;
+			}
+			return n;
+		}
+
+		/* instead of adding another permutation array, just use hash table defined above */
+		public float newPerlin(float x, float y, float z) {
+			int A, AA, AB, B, BA, BB;
+			float u = (float)Math.floor(x), v = (float)Math.floor(y), w = (float)Math.floor(z);
+			int X = (int)u & 255, Y = (int)v & 255, Z = (int)w & 255; // FIND UNIT CUBE THAT CONTAINS POINT
+			x -= u; // FIND RELATIVE X,Y,Z
+			y -= v; // OF POINT IN CUBE.
+			z -= w;
+			u = this.npfade(x); // COMPUTE FADE CURVES
+			v = this.npfade(y); // FOR EACH OF X,Y,Z.
+			w = this.npfade(z);
+			A = hash[X] + Y;
+			AA = hash[A] + Z;
+			AB = hash[A + 1] + Z; // HASH COORDINATES OF
+			B = hash[X + 1] + Y;
+			BA = hash[B] + Z;
+			BB = hash[B + 1] + Z; // THE 8 CUBE CORNERS,
+			return this.lerp(w, this.lerp(v, this.lerp(u, this.grad(hash[AA], x, y, z), // AND ADD
+					this.grad(hash[BA], x - 1, y, z)), // BLENDED
+					this.lerp(u, this.grad(hash[AB], x, y - 1, z), // RESULTS
+							this.grad(hash[BB], x - 1, y - 1, z))),// FROM 8
+					this.lerp(v, this.lerp(u, this.grad(hash[AA + 1], x, y, z - 1), // CORNERS
+							this.grad(hash[BA + 1], x - 1, y, z - 1)), // OF CUBE
+							this.lerp(u, this.grad(hash[AB + 1], x, y - 1, z - 1), this.grad(hash[BB + 1], x - 1, y - 1, z - 1))));
+		}
+
+		/**
+		 * Returns a vector/point/color in ca, using point hasharray directly
+		 */
+		protected static void cellNoiseV(float x, float y, float z, float[] ca) {
+			int xi = (int)Math.floor(x);
+			int yi = (int)Math.floor(y);
+			int zi = (int)Math.floor(z);
+			float[] p = AbstractNoiseFunc.hashPoint(xi, yi, zi);
+			ca[0] = p[0];
+			ca[1] = p[1];
+			ca[2] = p[2];
+		}
+
+		protected float lerp(float t, float a, float b) {
+			return a + t * (b - a);
+		}
+
+		protected float npfade(float t) {
+			return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
+		}
+
+		protected float grad(int hash, float x, float y, float z) {
+			int h = hash & 0x0F; // CONVERT LO 4 BITS OF HASH CODE
+			float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.
+			v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+			return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+		}
+
+		/**
+		 * Dot product of two vectors.
+		 * @param a
+		 *        the first vector
+		 * @param b
+		 *        the second vector
+		 * @return the dot product of two vectors
+		 */
+		protected float dot(float[] a, float[] b) {
+			return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+		}
+
+		protected float surve(float t) {
+			return t * t * (3.0f - 2.0f * t);
+		}
+
+		protected float at(float rx, float ry, float rz, float[] q) {
+			return rx * q[0] + ry * q[1] + rz * q[2];
+		}
+	}
+
+	/**
+	 * This interface is used for distance calculation classes. Distance metrics for voronoi. e parameter only used in
+	 * Minkovsky.
+	 */
+	interface IDistanceFunc {
+		/**
+		 * This method calculates the distance for voronoi algorithms.
+		 * @param x
+		 *        the x coordinate
+		 * @param y
+		 *        the y coordinate
+		 * @param z
+		 *        the z coordinate
+		 * @param e
+		 *        this parameter used in Monkovsky (no idea what it really is ;)
+		 * @return
+		 */
+		float execute(float x, float y, float z, float e);
+	}
+
+	interface IMusgraveFunction {
+		float execute(Structure tex, float x, float y, float z);
+	}
+}

+ 407 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java

@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneAnimation;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.PointLight;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.structures.Ipo;
+import com.jme3.scene.plugins.blender.structures.Modifier;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+import com.jme3.scene.plugins.ogre.AnimData;
+
+/**
+ * A class that is used in object calculations.
+ * @author Marcin Roguski
+ */
+public class ObjectHelper extends AbstractBlenderHelper {
+	private static final Logger			LOGGER		= Logger.getLogger(ObjectHelper.class.getName());
+
+	protected static final int		OBJECT_TYPE_EMPTY			= 0;
+	protected static final int		OBJECT_TYPE_MESH			= 1;
+	protected static final int		OBJECT_TYPE_CURVE			= 2;
+	protected static final int		OBJECT_TYPE_SURF			= 3;
+	protected static final int		OBJECT_TYPE_TEXT			= 4;
+	protected static final int		OBJECT_TYPE_METABALL		= 5;
+	protected static final int		OBJECT_TYPE_LAMP			= 10;
+	protected static final int		OBJECT_TYPE_CAMERA			= 11;
+	protected static final int		OBJECT_TYPE_WAVE			= 21;
+	protected static final int		OBJECT_TYPE_LATTICE			= 22;
+	protected static final int		OBJECT_TYPE_ARMATURE		= 25;
+	
+	/** This variable indicates if the Y asxis is the UP axis or not. */
+	protected boolean						fixUpAxis;
+	/** Quaternion used to rotate data when Y is up axis. */
+	protected Quaternion					upAxisRotationQuaternion;
+
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ObjectHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	/**
+	 * This method sets the Y is UP axis. By default the UP axis is Z (just like in blender).
+	 * @param fixUpAxis
+	 *        a variable that indicates if the Y asxis is the UP axis or not
+	 */
+	public void setyIsUpAxis(boolean fixUpAxis) {
+		this.fixUpAxis = fixUpAxis;
+		if(fixUpAxis) {
+			upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0);
+		}
+	}
+
+	/**
+	 * This method reads the given structure and createn an object that represents the data.
+	 * @param objectStructure
+	 *            the object's structure
+	 * @param dataRepository
+	 *            the data repository
+	 * @return blener's object representation
+	 * @throws BlenderFileException
+	 *             an exception is thrown when the given data is inapropriate
+	 */
+	public Object toObject(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
+		Object loadedResult = dataRepository.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+		if(loadedResult != null) {
+			return loadedResult;
+		}
+
+		dataRepository.pushParent(objectStructure);
+
+		ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+		ModifierHelper modifierHelper = dataRepository.getHelper(ModifierHelper.class);
+		ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class);
+		ConstraintHelper constraintHelper = dataRepository.getHelper(ConstraintHelper.class);
+
+		//get object data
+		int type = ((Number)objectStructure.getFieldValue("type")).intValue();
+		String name = objectStructure.getName();
+		LOGGER.log(Level.INFO, "Loading obejct: {0}", name);
+
+		//reading modifiers
+		modifierHelper.readModifiers(objectStructure, dataRepository);
+		Modifier objectAnimationModifier = objectHelper.readObjectAnimation(objectStructure, dataRepository);
+
+		//loading constraints connected with this object
+		constraintHelper.loadConstraints(objectStructure, dataRepository);
+
+		int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue();
+		boolean visible = (restrictflag & 0x01) != 0;
+		Object result = null;
+
+		Pointer pParent = (Pointer)objectStructure.getFieldValue("parent");
+		Object parent = dataRepository.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+		if(parent == null && !pParent.isNull()) {
+			Structure parentStructure = pParent.fetchData(dataRepository.getInputStream()).get(0);//TODO: moze byc wiecej rodzicow
+			parent = this.toObject(parentStructure, dataRepository);
+		}
+
+		Transform t = objectHelper.getTransformation(objectStructure);
+		
+		try {
+			switch(type) {
+				case OBJECT_TYPE_EMPTY:
+					LOGGER.log(Level.INFO, "Importing empty.");
+					Node empty = new Node(name);
+					empty.setLocalTransform(t);
+					result = empty;
+					break;
+				case OBJECT_TYPE_MESH:
+					LOGGER.log(Level.INFO, "Importing mesh.");
+					Node node = new Node(name);
+					node.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
+
+					//reading mesh
+					MeshHelper meshHelper = dataRepository.getHelper(MeshHelper.class);
+					Pointer pMesh = (Pointer)objectStructure.getFieldValue("data");
+					List<Structure> meshesArray = pMesh.fetchData(dataRepository.getInputStream());
+					List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), dataRepository);
+					for(Geometry geometry : geometries) {
+						node.attachChild(geometry);
+					}
+					node.setLocalTransform(t);
+
+					//applying all modifiers
+					List<Modifier> modifiers = dataRepository.getModifiers(objectStructure.getOldMemoryAddress(), null);
+					for(Modifier modifier : modifiers) {
+						modifierHelper.applyModifier(node, modifier, dataRepository);
+					}
+					//adding object animation modifier
+					if(objectAnimationModifier != null) {
+						node = modifierHelper.applyModifier(node, objectAnimationModifier, dataRepository);
+					}
+
+					//setting the parent
+					if(parent instanceof Node) {
+						((Node)parent).attachChild(node);
+					}
+					node.updateModelBound();//I prefer do calculate bounding box here than read it from the file
+					result = node;
+					break;
+				case OBJECT_TYPE_SURF:
+				case OBJECT_TYPE_CURVE:
+					LOGGER.log(Level.INFO, "Importing curve/nurb.");
+					Pointer pCurve = (Pointer)objectStructure.getFieldValue("data");
+					if(!pCurve.isNull()) {
+						CurvesHelper curvesHelper = dataRepository.getHelper(CurvesHelper.class);
+						Structure curveData = pCurve.fetchData(dataRepository.getInputStream()).get(0);
+						List<Geometry> curves = curvesHelper.toCurve(curveData, dataRepository);
+						result = new Node(name);
+						for(Geometry curve : curves) {
+							((Node)result).attachChild(curve);
+						}
+						((Node)result).setLocalTransform(t);
+					}
+					break;
+				case OBJECT_TYPE_LAMP:
+					LOGGER.log(Level.INFO, "Importing lamp.");
+					Pointer pLamp = (Pointer)objectStructure.getFieldValue("data");
+					if(!pLamp.isNull()) {
+						LightHelper lightHelper = dataRepository.getHelper(LightHelper.class);
+						List<Structure> lampsArray = pLamp.fetchData(dataRepository.getInputStream());
+						Light light = lightHelper.toLight(lampsArray.get(0), dataRepository);
+						if(light!=null) {
+							light.setName(name);
+						}
+						if(light instanceof PointLight) {
+							((PointLight)light).setPosition(t.getTranslation());
+						} else if(light instanceof DirectionalLight) {
+							Quaternion quaternion = t.getRotation();
+							Vector3f[] axes = new Vector3f[3];
+							quaternion.toAxes(axes);
+							((DirectionalLight)light).setDirection(axes[2].negate());//-Z is the direction axis of area lamp in blender
+						} else {
+							LOGGER.log(Level.WARNING, "Unknown type of light: {0}", light);
+						}
+						result = light;
+					}
+					break;
+				case OBJECT_TYPE_CAMERA:
+					Pointer pCamera = (Pointer)objectStructure.getFieldValue("data");
+					if(!pCamera.isNull()) {
+						CameraHelper cameraHelper = dataRepository.getHelper(CameraHelper.class);
+						List<Structure> camerasArray = pCamera.fetchData(dataRepository.getInputStream());
+						Camera camera = cameraHelper.toCamera(camerasArray.get(0));
+						camera.setLocation(t.getTranslation());
+						camera.setRotation(t.getRotation());
+						result = camera;
+					}
+					break;
+				case OBJECT_TYPE_ARMATURE:
+					LOGGER.log(Level.INFO, "Importing armature.");
+					Pointer pArmature = (Pointer)objectStructure.getFieldValue("data");
+					List<Structure> armaturesArray = pArmature.fetchData(dataRepository.getInputStream());//TODO: moze byc wiecej???
+					result = armatureHelper.toArmature(armaturesArray.get(0), dataRepository);
+					break;
+				default:
+					LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);
+			}
+		} finally {
+			dataRepository.popParent();
+		}
+		if(result != null) {
+			dataRepository.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
+		}
+		return result;
+	}
+	
+	/**
+	 * This method calculates local transformation for the object. Parentage is taken under consideration.
+	 * @param objectStructure
+	 *        the object's structure
+	 * @return objects transformation relative to its parent
+	 */
+	@SuppressWarnings("unchecked")
+	public Transform getTransformation(Structure objectStructure) {
+		DynamicArray<Number> loc = (DynamicArray<Number>)objectStructure.getFieldValue("loc");
+		DynamicArray<Number> size = (DynamicArray<Number>)objectStructure.getFieldValue("size");
+		DynamicArray<Number> rot = (DynamicArray<Number>)objectStructure.getFieldValue("rot");
+
+		Pointer parent = (Pointer) objectStructure.getFieldValue("parent");
+		Matrix4f parentInv = parent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv");
+		
+		Matrix4f globalMatrix = new Matrix4f();
+		globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue());
+		globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue()));
+		Matrix4f localMatrix = parentInv.mult(globalMatrix);
+
+		Vector3f translation = localMatrix.toTranslationVector();
+		Quaternion rotation = localMatrix.toRotationQuat();
+		//getting the scale
+		float scaleX = (float) Math.sqrt(parentInv.m00 * parentInv.m00 + parentInv.m10 * parentInv.m10 + parentInv.m20 * parentInv.m20);
+		float scaleY = (float) Math.sqrt(parentInv.m01 * parentInv.m01 + parentInv.m11 * parentInv.m11 + parentInv.m21 * parentInv.m21);
+		float scaleZ = (float) Math.sqrt(parentInv.m02 * parentInv.m02 + parentInv.m12 * parentInv.m12 + parentInv.m22 * parentInv.m22);
+		Vector3f scale = new Vector3f(size.get(0).floatValue() * scaleX, 
+									  size.get(1).floatValue() * scaleY, 
+									  size.get(2).floatValue() * scaleZ);
+		if(fixUpAxis) {
+			float y = translation.y;
+			translation.y = translation.z;
+			translation.z = y;
+			rotation.multLocal(this.upAxisRotationQuaternion);
+		}
+		Transform t = new Transform(translation, rotation);
+		t.setScale(scale);
+		return t;
+	}
+
+	/**
+	 * This method returns the transformation matrix of the given object structure.
+	 * @param objectStructure
+	 *        the structure with object's data
+	 * @return object's transformation matrix
+	 */
+	public Matrix4f getTransformationMatrix(Structure objectStructure) {
+		return this.getMatrix(objectStructure, "obmat");
+	}
+
+	/**
+	 * This method returns the matrix of a given name for the given object structure.
+	 * @param objectStructure
+	 *        the structure with object's data
+	 * @param matrixName
+	 * 		  the name of the matrix structure
+	 * @return object's matrix
+	 */
+	@SuppressWarnings("unchecked")
+	protected Matrix4f getMatrix(Structure objectStructure, String matrixName) {
+		Matrix4f result = new Matrix4f();
+		DynamicArray<Number> obmat = (DynamicArray<Number>)objectStructure.getFieldValue(matrixName);
+		for(int i = 0; i < 4; ++i) {
+			for(int j = 0; j < 4; ++j) {
+				result.set(i, j, obmat.get(j, i).floatValue());
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * This method reads animation of the object itself (without bones) and stores it as an ArmatureModifierData
+	 * modifier. The animation is returned as a modifier. It should be later applied regardless other modifiers. The
+	 * reason for this is that object may not have modifiers added but it's animation should be working.
+	 * @param objectStructure
+	 *        the structure of the object
+	 * @param dataRepository
+	 *        the data repository
+	 * @return animation modifier is returned, it should be separately applied when the object is loaded
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blender file is somehow corrupted
+	 */
+	public Modifier readObjectAnimation(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException {
+		Pointer pIpo = (Pointer)objectStructure.getFieldValue("ipo");
+		if(!pIpo.isNull()) {
+			//check if there is an action name connected with this ipo
+			String objectAnimationName = null;
+			List<FileBlockHeader> actionBlocks = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
+			for(FileBlockHeader actionBlock : actionBlocks) {
+				Structure action = actionBlock.getStructure(dataRepository);
+				List<Structure> actionChannels = ((Structure)action.getFieldValue("chanbase")).evaluateListBase(dataRepository);
+				if(actionChannels.size() == 1) {//object's animtion action has only one channel
+					Pointer pChannelIpo = (Pointer)actionChannels.get(0).getFieldValue("ipo");
+					if(pChannelIpo.equals(pIpo)) {
+						objectAnimationName = action.getName();
+						break;
+					}
+				}
+			}
+			
+			String objectName = objectStructure.getName();
+			if(objectAnimationName == null) {//set the object's animation name to object's name
+				objectAnimationName = objectName;
+			}
+
+			IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class);
+			Structure ipoStructure = pIpo.fetchData(dataRepository.getInputStream()).get(0);
+			Ipo ipo = ipoHelper.createIpo(ipoStructure, dataRepository);
+			int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, objectAnimationName);
+			if(animationFrames == null) {//if the name was created here there are no frames set for the animation
+				animationFrames = new int[] {1, ipo.getLastFrame()};
+			}
+			int fps = dataRepository.getBlenderKey().getFps();
+			float start = (float)animationFrames[0] / (float)fps;
+			float stop = (float)animationFrames[1] / (float)fps;
+
+			//calculating track for the only bone in this skeleton
+			BoneTrack[] tracks = new BoneTrack[1];
+			tracks[0] = ipo.calculateTrack(0, animationFrames[0], animationFrames[1], fps);
+
+			BoneAnimation boneAnimation = new BoneAnimation(objectAnimationName, stop - start);
+			boneAnimation.setTracks(tracks);
+			ArrayList<BoneAnimation> animations = new ArrayList<BoneAnimation>(1);
+			animations.add(boneAnimation);
+
+			//preparing the object's bone
+			Transform t = this.getTransformation(objectStructure);
+			Bone bone = new Bone(null);
+			bone.setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale());
+
+			return new Modifier(Modifier.ARMATURE_MODIFIER_DATA, new AnimData(new Skeleton(new Bone[] {bone}), animations), null);
+		}
+		return null;
+	}
+
+	@Override
+	public void clearState() {
+		fixUpAxis = false;
+	}
+}

+ 118 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ParticlesHelper.java

@@ -0,0 +1,118 @@
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.util.logging.Logger;
+
+import com.jme3.effect.EmitterMeshConvexHullShape;
+import com.jme3.effect.EmitterMeshFaceShape;
+import com.jme3.effect.EmitterMeshVertexShape;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+public class ParticlesHelper extends AbstractBlenderHelper {
+	private static final Logger			LOGGER		= Logger.getLogger(ParticlesHelper.class.getName());
+	
+	// part->type
+	public static final int PART_EMITTER	=	0;
+	public static final int PART_REACTOR	=	1;
+	public static final int PART_HAIR		=	2;
+	public static final int PART_FLUID		=	3;
+	
+	// part->flag
+	public static final int PART_REACT_STA_END	=1;
+	public static final int PART_REACT_MULTIPLE	=2;
+	public static final int PART_LOOP			=4;
+	//public static final int PART_LOOP_INSTANT	=8;
+	public static final int PART_HAIR_GEOMETRY	=16;
+	public static final int PART_UNBORN			=32;		//show unborn particles
+	public static final int PART_DIED			=64;		//show died particles
+	public static final int PART_TRAND			=128;	
+	public static final int PART_EDISTR			=256;		// particle/face from face areas
+	public static final int PART_STICKY			=512;		//collided particles can stick to collider
+	public static final int PART_DIE_ON_COL		=1<<12;
+	public static final int PART_SIZE_DEFL		=1<<13; 	// swept sphere deflections
+	public static final int PART_ROT_DYN		=1<<14;	// dynamic rotation
+	public static final int PART_SIZEMASS		=1<<16;
+	public static final int PART_ABS_LENGTH		=1<<15;
+	public static final int PART_ABS_TIME		=1<<17;
+	public static final int PART_GLOB_TIME		=1<<18;
+	public static final int PART_BOIDS_2D		=1<<19;
+	public static final int PART_BRANCHING		=1<<20;
+	public static final int PART_ANIM_BRANCHING	=1<<21;
+	public static final int PART_SELF_EFFECT	=1<<22;
+	public static final int PART_SYMM_BRANCHING	=1<<24;
+	public static final int PART_HAIR_BSPLINE	=1024;
+	public static final int PART_GRID_INVERT	=1<<26;
+	public static final int PART_CHILD_EFFECT	=1<<27;
+	public static final int PART_CHILD_SEAMS	=1<<28;
+	public static final int PART_CHILD_RENDER	=1<<29;
+	public static final int PART_CHILD_GUIDE	=1<<30;
+	
+	// part->from
+	public static final int PART_FROM_VERT		=0;
+	public static final int PART_FROM_FACE		=1;
+	public static final int PART_FROM_VOLUME	=2;
+	public static final int PART_FROM_PARTICLE	=3;
+	public static final int PART_FROM_CHILD		=4;
+	
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
+	 * different blender versions.
+	 * @param blenderVersion
+	 *        the version read from the blend file
+	 */
+	public ParticlesHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	@SuppressWarnings("unchecked")
+	public ParticleEmitter toParticleEmitter(Structure particleSystem, DataRepository dataRepository) throws BlenderFileException {
+		ParticleEmitter result = null;
+		Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part");
+		if(!pParticleSettings.isNull()) {
+			Structure particleSettings = pParticleSettings.fetchData(dataRepository.getInputStream()).get(0);
+			int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue();
+			result = new ParticleEmitter(particleSettings.getName(), Type.Triangle, totPart);
+			
+			//setting the emitters shape (the shapes meshes will be set later during modifier applying operation)
+			int from = ((Number)particleSettings.getFieldValue("from")).intValue();
+			switch(from) {
+				case PART_FROM_VERT:
+					result.setShape(new EmitterMeshVertexShape());
+					break;
+				case PART_FROM_FACE:
+					result.setShape(new EmitterMeshFaceShape());
+					break;
+				case PART_FROM_VOLUME:
+					result.setShape(new EmitterMeshConvexHullShape());
+					break;
+				default:
+					LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter): " + from);
+			}
+			
+			//reading acceleration
+			DynamicArray<Number> acc = (DynamicArray<Number>) particleSettings.getFieldValue("acc");
+			result.setInitialVelocity(new Vector3f(acc.get(0).floatValue(), acc.get(1).floatValue(), acc.get(2).floatValue()));
+			result.setGravity(0);//by default gravity is set to 0.1f so we need to disable it completely
+			// 2x2 texture animation
+			result.setImagesX(2);
+			result.setImagesY(2);
+			result.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f));   // red
+			result.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow
+			result.setStartSize(1.5f);
+			result.setEndSize(0.1f);
+			
+			result.setLowLife(0.5f);
+		    result.setHighLife(3f);
+		    result.setVelocityVariation(0.3f);
+		}
+		return result;
+	}
+}

+ 1818 - 0
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/TextureHelper.java

@@ -0,0 +1,1818 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.helpers.v249;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.asset.TextureKey;
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.helpers.NoiseHelper;
+import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.utils.BlenderInputStream;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.plugins.AWTLoader;
+import com.jme3.texture.plugins.DDSLoader;
+import com.jme3.texture.plugins.TGALoader;
+import com.jme3.util.BufferUtils;
+
+/**
+ * A class that is used in texture calculations.
+ * 
+ * @author Marcin Roguski
+ */
+public class TextureHelper extends AbstractBlenderHelper {
+	private static final Logger	LOGGER				= Logger.getLogger(TextureHelper.class.getName());
+
+	// texture types
+	public static final int		TEX_NONE			= 0;
+	public static final int		TEX_CLOUDS			= 1;
+	public static final int		TEX_WOOD			= 2;
+	public static final int		TEX_MARBLE			= 3;
+	public static final int		TEX_MAGIC			= 4;
+	public static final int		TEX_BLEND			= 5;
+	public static final int		TEX_STUCCI			= 6;
+	public static final int		TEX_NOISE			= 7;
+	public static final int		TEX_IMAGE			= 8;
+	public static final int		TEX_PLUGIN			= 9;
+	public static final int		TEX_ENVMAP			= 10;
+	public static final int		TEX_MUSGRAVE		= 11;
+	public static final int		TEX_VORONOI			= 12;
+	public static final int		TEX_DISTNOISE		= 13;
+
+	// mapto
+	public static final int		MAP_COL				= 1;
+	public static final int		MAP_NORM			= 2;
+	public static final int		MAP_COLSPEC			= 4;
+	public static final int		MAP_COLMIR			= 8;
+	public static final int		MAP_VARS			= 0xFFF0;
+	public static final int		MAP_REF				= 16;
+	public static final int		MAP_SPEC			= 32;
+	public static final int		MAP_EMIT			= 64;
+	public static final int		MAP_ALPHA			= 128;
+	public static final int		MAP_HAR				= 256;
+	public static final int		MAP_RAYMIRR			= 512;
+	public static final int		MAP_TRANSLU			= 1024;
+	public static final int		MAP_AMB				= 2048;
+	public static final int		MAP_DISPLACE		= 4096;
+	public static final int		MAP_WARP			= 8192;
+	public static final int		MAP_LAYER			= 16384;
+
+	// blendtypes
+	public static final int		MTEX_BLEND			= 0;
+	public static final int		MTEX_MUL			= 1;
+	public static final int		MTEX_ADD			= 2;
+	public static final int		MTEX_SUB			= 3;
+	public static final int		MTEX_DIV			= 4;
+	public static final int		MTEX_DARK			= 5;
+	public static final int		MTEX_DIFF			= 6;
+	public static final int		MTEX_LIGHT			= 7;
+	public static final int		MTEX_SCREEN			= 8;
+	public static final int		MTEX_OVERLAY		= 9;
+	public static final int		MTEX_BLEND_HUE		= 10;
+	public static final int		MTEX_BLEND_SAT		= 11;
+	public static final int		MTEX_BLEND_VAL		= 12;
+	public static final int		MTEX_BLEND_COLOR	= 13;
+	public static final int		MTEX_NUM_BLENDTYPES	= 14;
+
+	// variables used in rampBlend method
+	public static final int		MA_RAMP_BLEND		= 0;
+	public static final int		MA_RAMP_ADD			= 1;
+	public static final int		MA_RAMP_MULT		= 2;
+	public static final int		MA_RAMP_SUB			= 3;
+	public static final int		MA_RAMP_SCREEN		= 4;
+	public static final int		MA_RAMP_DIV			= 5;
+	public static final int		MA_RAMP_DIFF		= 6;
+	public static final int		MA_RAMP_DARK		= 7;
+	public static final int		MA_RAMP_LIGHT		= 8;
+	public static final int		MA_RAMP_OVERLAY		= 9;
+	public static final int		MA_RAMP_DODGE		= 10;
+	public static final int		MA_RAMP_BURN		= 11;
+	public static final int		MA_RAMP_HUE			= 12;
+	public static final int		MA_RAMP_SAT			= 13;
+	public static final int		MA_RAMP_VAL			= 14;
+	public static final int		MA_RAMP_COLOR		= 15;
+
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
+	 * versions.
+	 * 
+	 * @param blenderVersion
+	 *            the version read from the blend file
+	 */
+	public TextureHelper(String blenderVersion) {
+		super(blenderVersion);
+	}
+
+	/**
+	 * This class returns a texture read from the file or from packed blender data. The returned texture has the name set to the value of
+	 * its blender type.
+	 * 
+	 * @param tex
+	 *            texture structure filled with data
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the texture that can be used by JME engine
+	 * @throws BlenderFileException
+	 *             this exception is thrown when the blend file structure is somehow invalid or corrupted
+	 */
+	public Texture getTexture(Structure tex, DataRepository dataRepository) throws BlenderFileException {
+		Texture result = (Texture) dataRepository.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+		if (result != null) {
+			return result;
+		}
+		int type = ((Number) tex.getFieldValue("type")).intValue();
+		int width = dataRepository.getBlenderKey().getGeneratedTextureWidth();
+		int height = dataRepository.getBlenderKey().getGeneratedTextureHeight();
+
+		switch (type) {
+			case TEX_NONE:// No texture, do nothing
+				break;
+			case TEX_IMAGE:// (it is first because probably this will be most commonly used)
+				Pointer pImage = (Pointer) tex.getFieldValue("ima");
+				Structure image = pImage.fetchData(dataRepository.getInputStream()).get(0);
+				result = this.getTextureFromImage(image, dataRepository);
+				break;
+			case TEX_CLOUDS:
+				result = this.clouds(tex, width, height, dataRepository);
+				break;
+			case TEX_WOOD:
+				result = this.wood(tex, width, height, dataRepository);
+				break;
+			case TEX_MARBLE:
+				result = this.marble(tex, width, height, dataRepository);
+				break;
+			case TEX_MAGIC:
+				result = this.magic(tex, width, height, dataRepository);
+				break;
+			case TEX_BLEND:
+				result = this.blend(tex, width, height, dataRepository);
+				break;
+			case TEX_STUCCI:
+				result = this.stucci(tex, width, height, dataRepository);
+				break;
+			case TEX_NOISE:
+				result = this.texnoise(tex, width, height, dataRepository);
+				break;
+			case TEX_MUSGRAVE:
+				result = this.musgrave(tex, width, height, dataRepository);
+				break;
+			case TEX_VORONOI:
+				result = this.voronoi(tex, width, height, dataRepository);
+				break;
+			case TEX_DISTNOISE:
+				result = this.distnoise(tex, width, height, dataRepository);
+				break;
+			case TEX_PLUGIN:
+			case TEX_ENVMAP:// TODO: implement envmap texture
+				LOGGER.log(Level.WARNING, "Unsupported texture type: " + type + " for texture: " + tex.getName());
+				break;
+			default:
+				throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName());
+		}
+		if (result != null) {
+			result.setName(String.valueOf(type));
+			result.setWrap(WrapMode.Repeat);
+		}
+		return result;
+	}
+
+	/**
+	 * This method generates the clouds texture. The result is one pixel.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of texture (in pixels)
+	 * @param height
+	 *            the height of texture (in pixels)
+	 * @param dataRepository
+	 *            the data repository
+	 * @return generated texture
+	 */
+	protected Texture clouds(Structure tex, int width, int height, DataRepository dataRepository) {
+		// preparing the proper data
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		float wDelta = 1.0f / width, hDelta = 1.0f / height;
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexResult texres = new TexResult();
+
+		// reading the data from the texture structure
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		int noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
+		int noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+		int noiseType = ((Number) tex.getFieldValue("noisetype")).intValue();
+		float contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
+		float bright = ((Number) tex.getFieldValue("bright")).floatValue();
+		boolean isHard = noiseType != com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_NOISESOFT;
+		int sType = ((Number) tex.getFieldValue("stype")).intValue();
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = sType == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_COLOR || colorBand != null ? Format.RGB8
+				: Format.Luminance8;
+		int bytesPerPixel = sType == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_COLOR || colorBand != null ? 3 : 1;
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;// x
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;// y (z is always = 0)
+
+				texres.tin = noiseHelper.bliGTurbulence(noisesize, texvec[0], texvec[1], texvec[2], noiseDepth, isHard, noiseBasis);
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					if (texres.nor != null) {
+						float nabla = ((Number) tex.getFieldValue("nabla")).floatValue();
+						// calculate bumpnormal
+						texres.nor[0] = noiseHelper.bliGTurbulence(noisesize, texvec[0] + nabla, texvec[1], texvec[2], noiseDepth, isHard,
+								noiseBasis);
+						texres.nor[1] = noiseHelper.bliGTurbulence(noisesize, texvec[0], texvec[1] + nabla, texvec[2], noiseDepth, isHard,
+								noiseBasis);
+						texres.nor[2] = noiseHelper.bliGTurbulence(noisesize, texvec[0], texvec[1], texvec[2] + nabla, noiseDepth, isHard,
+								noiseBasis);
+						noiseHelper.texNormalDerivate(colorBand, texres, dataRepository);
+					}
+					noiseHelper.brightnesAndContrastRGB(tex, texres);
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else if (sType == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_COLOR) {
+					// in this case, int. value should really be computed from color,
+					// and bumpnormal from that, would be too slow, looks ok as is
+					texres.tr = texres.tin;
+					texres.tg = noiseHelper.bliGTurbulence(noisesize, texvec[1], texvec[0], texvec[2], noiseDepth, isHard, noiseBasis);
+					texres.tb = noiseHelper.bliGTurbulence(noisesize, texvec[1], texvec[2], texvec[0], noiseDepth, isHard, noiseBasis);
+					noiseHelper.brightnesAndContrastRGB(tex, texres);
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					noiseHelper.brightnesAndContrast(texres, contrast, bright);
+					data.put((byte) (texres.tin * 255));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method generates the wood texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	protected Texture wood(Structure tex, int width, int height, DataRepository dataRepository) {
+		// preparing the proper data
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		float contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
+		float bright = ((Number) tex.getFieldValue("bright")).floatValue();
+		float nabla = ((Number) tex.getFieldValue("nabla")).floatValue();
+		float wDelta = 1.0f / width, hDelta = 1.0f / height;
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexResult texres = new TexResult();
+		int halfW = width;
+		int halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = colorBand != null ? Format.RGB8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 3 : 1;
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;
+				texres.tin = noiseHelper.woodInt(tex, texvec[0], texvec[1], texvec[2], dataRepository);
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					if (texres.nor != null) {// calculate bumpnormal
+						texres.nor[0] = noiseHelper.woodInt(tex, texvec[0] + nabla, texvec[1], texvec[2], dataRepository);
+						texres.nor[1] = noiseHelper.woodInt(tex, texvec[0], texvec[1] + nabla, texvec[2], dataRepository);
+						texres.nor[2] = noiseHelper.woodInt(tex, texvec[0], texvec[1], texvec[2] + nabla, dataRepository);
+						noiseHelper.texNormalDerivate(colorBand, texres, dataRepository);
+					}
+					noiseHelper.brightnesAndContrastRGB(tex, texres);
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					noiseHelper.brightnesAndContrast(texres, contrast, bright);
+					data.put((byte) (texres.tin * 255));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method generates the marble texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	protected Texture marble(Structure tex, int width, int height, DataRepository dataRepository) {
+		// preparing the proper data
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		float contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
+		float bright = ((Number) tex.getFieldValue("bright")).floatValue();
+		float nabla = ((Number) tex.getFieldValue("nabla")).floatValue();
+		float wDelta = 1.0f / width, hDelta = 1.0f / height;
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexResult texres = new TexResult();
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = colorBand != null ? Format.RGB8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 3 : 1;
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;
+				texres.tin = noiseHelper.marbleInt(tex, texvec[0], texvec[1], texvec[2], dataRepository);
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					if (texres.nor != null) {// calculate bumpnormal
+						texres.nor[0] = noiseHelper.marbleInt(tex, texvec[0] + nabla, texvec[1], texvec[2], dataRepository);
+						texres.nor[1] = noiseHelper.marbleInt(tex, texvec[0], texvec[1] + nabla, texvec[2], dataRepository);
+						texres.nor[2] = noiseHelper.marbleInt(tex, texvec[0], texvec[1], texvec[2] + nabla, dataRepository);
+						noiseHelper.texNormalDerivate(colorBand, texres, dataRepository);
+					}
+
+					noiseHelper.brightnesAndContrastRGB(tex, texres);
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					noiseHelper.brightnesAndContrast(texres, contrast, bright);
+					data.put((byte) (texres.tin * 255.0f));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method generates the magic texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	protected Texture magic(Structure tex, int width, int height, DataRepository dataRepository) {
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		float x, y, z, turb;
+		int noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
+		float turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f;
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexResult texres = new TexResult();
+		float wDelta = 1.0f / width, hDelta = 1.0f / height;
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * 4);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				turb = turbul;
+				texvec[1] = hDelta * j;
+				x = (float) Math.sin((texvec[0] + texvec[1]) * 5.0f);// in blender: Math.sin((texvec[0] + texvec[1] + texvec[2]) * 5.0f);
+				y = (float) Math.cos((-texvec[0] + texvec[1]) * 5.0f);// in blender: Math.cos((-texvec[0] + texvec[1] - texvec[2]) * 5.0f);
+				z = -(float) Math.cos((-texvec[0] - texvec[1]) * 5.0f);// in blender: Math.cos((-texvec[0] - texvec[1] + texvec[2]) * 5.0f);
+				
+				if (colorBand != null) {
+					texres.tin = 0.3333f * (x + y + z);
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+				} else {
+					if (noisedepth > 0) {
+						x *= turb;
+						y *= turb;
+						z *= turb;
+						y = -(float) Math.cos(x - y + z) * turb;
+						if (noisedepth > 1) {
+							x = (float) Math.cos(x - y - z) * turb;
+							if (noisedepth > 2) {
+								z = (float) Math.sin(-x - y - z) * turb;
+								if (noisedepth > 3) {
+									x = -(float) Math.cos(-x + y - z) * turb;
+									if (noisedepth > 4) {
+										y = -(float) Math.sin(-x + y + z) * turb;
+										if (noisedepth > 5) {
+											y = -(float) Math.cos(-x + y + z) * turb;
+											if (noisedepth > 6) {
+												x = (float) Math.cos(x + y + z) * turb;
+												if (noisedepth > 7) {
+													z = (float) Math.sin(x + y - z) * turb;
+													if (noisedepth > 8) {
+														x = -(float) Math.cos(-x - y + z) * turb;
+														if (noisedepth > 9) {
+															y = -(float) Math.sin(x - y + z) * turb;
+														}
+													}
+												}
+											}
+										}
+									}
+								}
+							}
+						}
+					}
+
+					if (turb != 0.0f) {
+						turb *= 2.0f;
+						x /= turb;
+						y /= turb;
+						z /= turb;
+					}
+					texres.tr = 0.5f - x;
+					texres.tg = 0.5f - y;
+					texres.tb = 0.5f - z;
+				}
+				noiseHelper.brightnesAndContrastRGB(tex, texres);
+				data.put((byte) (texres.tin * 255));
+				data.put((byte) (texres.tb * 255));
+				data.put((byte) (texres.tg * 255));
+				data.put((byte) (texres.tr * 255));
+			}
+		}
+		return new Texture2D(new Image(Format.ABGR8, width, height, data));
+	}
+
+	/**
+	 * This method generates the blend texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	protected Texture blend(Structure tex, int width, int height, DataRepository dataRepository) {
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		int flag = ((Number) tex.getFieldValue("flag")).intValue();
+		int stype = ((Number) tex.getFieldValue("stype")).intValue();
+		float contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
+		float brightness = ((Number) tex.getFieldValue("bright")).floatValue();
+		float wDelta = 1.0f / width, hDelta = 1.0f / height, x, y, t;
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexResult texres = new TexResult();
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = colorBand != null ? Format.RGB8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 3 : 1;
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;
+				if ((flag & com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_FLIPBLEND) != 0) {
+					x = texvec[1];
+					y = texvec[0];
+				} else {
+					x = texvec[0];
+					y = texvec[1];
+				}
+
+				if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_LIN) { /* lin */
+					texres.tin = (1.0f + x) / 2.0f;
+				} else if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_QUAD) { /* quad */
+					texres.tin = (1.0f + x) / 2.0f;
+					if (texres.tin < 0.0f) {
+						texres.tin = 0.0f;
+					} else {
+						texres.tin *= texres.tin;
+					}
+				} else if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_EASE) { /* ease */
+					texres.tin = (1.0f + x) / 2.0f;
+					if (texres.tin <= 0.0f) {
+						texres.tin = 0.0f;
+					} else if (texres.tin >= 1.0f) {
+						texres.tin = 1.0f;
+					} else {
+						t = texres.tin * texres.tin;
+						texres.tin = 3.0f * t - 2.0f * t * texres.tin;
+					}
+				} else if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_DIAG) { /* diag */
+					texres.tin = (2.0f + x + y) / 4.0f;
+				} else if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_RAD) { /* radial */
+					texres.tin = (float) Math.atan2(y, x) / FastMath.TWO_PI + 0.5f;
+				} else { /* sphere TEX_SPHERE */
+					texres.tin = 1.0f - (float) Math.sqrt(x * x + y * y + texvec[2] * texvec[2]);
+					if (texres.tin < 0.0f) {
+						texres.tin = 0.0f;
+					}
+					if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_HALO) {
+						texres.tin *= texres.tin;
+					} /* halo */
+				}
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					noiseHelper.brightnesAndContrastRGB(tex, texres);
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					noiseHelper.brightnesAndContrast(texres, contrast, brightness);
+					data.put((byte) (texres.tin * 255.0f));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method generates the stucci texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	protected Texture stucci(Structure tex, int width, int height, DataRepository dataRepository) {
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+		int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue();
+		float turbul = ((Number) tex.getFieldValue("turbul")).floatValue();
+		boolean isHard = noisetype != com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_NOISESOFT;
+		int stype = ((Number) tex.getFieldValue("stype")).intValue();
+
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		float[] texvec = new float[] { 0, 0, 0 };
+		TexResult texres = new TexResult();
+		float wDelta = 1.0f / width, hDelta = 1.0f / height, b2, ofs;
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = colorBand != null ? Format.RGB8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 3 : 1;
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i;// x
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j;// y (z is always = 0)
+				b2 = noiseHelper.bliGNoise(noisesize, texvec[0], texvec[1], texvec[2], isHard, noisebasis);
+
+				ofs = turbul / 200.0f;
+
+				if (stype != 0) {
+					ofs *= b2 * b2;
+				}
+
+				texres.tin = noiseHelper.bliGNoise(noisesize, texvec[0], texvec[1], texvec[2] + ofs, isHard, noisebasis);// ==nor[2]
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					if (texres.nor != null) {
+						texres.nor[0] = noiseHelper.bliGNoise(noisesize, texvec[0] + ofs, texvec[1], texvec[2], isHard, noisebasis);
+						texres.nor[1] = noiseHelper.bliGNoise(noisesize, texvec[0], texvec[1] + ofs, texvec[2], isHard, noisebasis);
+						texres.nor[2] = texres.tin;
+						noiseHelper.texNormalDerivate(colorBand, texres, dataRepository);
+
+						if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_WALLOUT) {
+							texres.nor[0] = -texres.nor[0];
+							texres.nor[1] = -texres.nor[1];
+							texres.nor[2] = -texres.nor[2];
+						}
+					}
+				}
+
+				if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_WALLOUT) {
+					texres.tin = 1.0f - texres.tin;
+				}
+				if (texres.tin < 0.0f) {
+					texres.tin = 0.0f;
+				}
+				if (colorBand != null) {
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					data.put((byte) (texres.tin * 255.0f));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method generates the noise texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	// TODO: correct this one, so it looks more like the texture generated by blender
+	protected Texture texnoise(Structure tex, int width, int height, DataRepository dataRepository) {
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		float div = 3.0f;
+		int val, ran, loop;
+		int noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue();
+		float contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
+		float brightness = ((Number) tex.getFieldValue("bright")).floatValue();
+		TexResult texres = new TexResult();
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = colorBand != null ? Format.RGB8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 3 : 1;
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			for (int j = -halfH; j < halfH; ++j) {
+				ran = FastMath.rand.nextInt();// BLI_rand();
+				val = ran & 3;
+
+				loop = noisedepth;
+				while (loop-- != 0) {
+					ran = ran >> 2;
+					val *= ran & 3;
+					div *= 3.0f;
+				}
+				texres.tin = val;// / div;
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					noiseHelper.brightnesAndContrastRGB(tex, texres);
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					noiseHelper.brightnesAndContrast(texres, contrast, brightness);
+					data.put((byte) (texres.tin * 255.0f));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method generates the musgrave texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	protected Texture musgrave(Structure tex, int width, int height, DataRepository dataRepository) {
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		int stype = ((Number) tex.getFieldValue("stype")).intValue();
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		TexResult texres = new TexResult();
+		float[] texvec = new float[] { 0, 0, 0 };
+		float wDelta = 1.0f / width, hDelta = 1.0f / height;
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = colorBand != null ? Format.RGB8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 3 : 1;
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i / noisesize;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j / noisesize;
+				switch (stype) {
+					case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_MFRACTAL:
+					case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_FBM:
+						noiseHelper.mgMFractalOrfBmTex(tex, texvec, colorBand, texres, dataRepository);
+						break;
+					case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_RIDGEDMF:
+					case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_HYBRIDMF:
+						noiseHelper.mgRidgedOrHybridMFTex(tex, texvec, colorBand, texres, dataRepository);
+						break;
+					case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_HTERRAIN:
+						noiseHelper.mgHTerrainTex(tex, texvec, colorBand, texres, dataRepository);
+						break;
+					default:
+						throw new IllegalStateException("Unknown type of musgrave texture: " + stype);
+				}
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					data.put((byte) (texres.tin * 255.0f));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method generates the voronoi texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	protected Texture voronoi(Structure tex, int width, int height, DataRepository dataRepository) {
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		float vn_w1 = ((Number) tex.getFieldValue("vn_w1")).floatValue();
+		float vn_w2 = ((Number) tex.getFieldValue("vn_w2")).floatValue();
+		float vn_w3 = ((Number) tex.getFieldValue("vn_w3")).floatValue();
+		float vn_w4 = ((Number) tex.getFieldValue("vn_w4")).floatValue();
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		float nabla = ((Number) tex.getFieldValue("nabla")).floatValue();
+		float ns_outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue();
+		float vn_mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue();
+		int vn_distm = ((Number) tex.getFieldValue("vn_distm")).intValue();
+		int vn_coltype = ((Number) tex.getFieldValue("vn_coltype")).intValue();
+		float contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
+		float brightness = ((Number) tex.getFieldValue("bright")).floatValue();
+
+		TexResult texres = new TexResult();
+		float[] texvec = new float[] { 0, 0, 0 };
+		float wDelta = 1.0f / width, hDelta = 1.0f / height;
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = vn_coltype != 0 || colorBand != null ? Format.RGB8 : Format.Luminance8;
+		int bytesPerPixel = vn_coltype != 0 || colorBand != null ? 3 : 1;
+
+		float[] da = new float[4], pa = new float[12]; /* distance and point coordinate arrays of 4 nearest neighbours */
+		float[] ca = vn_coltype != 0 ? new float[3] : null; // cell color
+		float aw1 = FastMath.abs(vn_w1);
+		float aw2 = FastMath.abs(vn_w2);
+		float aw3 = FastMath.abs(vn_w3);
+		float aw4 = FastMath.abs(vn_w4);
+		float sc = aw1 + aw2 + aw3 + aw4;
+		if (sc != 0.f) {
+			sc = ns_outscale / sc;
+		}
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i / noisesize;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j / noisesize;
+
+				noiseHelper.voronoi(texvec[0], texvec[1], texvec[2], da, pa, vn_mexp, vn_distm);
+				texres.tin = sc * FastMath.abs(vn_w1 * da[0] + vn_w2 * da[1] + vn_w3 * da[2] + vn_w4 * da[3]);
+				if (vn_coltype != 0) {
+					noiseHelper.cellNoiseV(pa[0], pa[1], pa[2], ca);
+					texres.tr = aw1 * ca[0];
+					texres.tg = aw1 * ca[1];
+					texres.tb = aw1 * ca[2];
+					noiseHelper.cellNoiseV(pa[3], pa[4], pa[5], ca);
+					texres.tr += aw2 * ca[0];
+					texres.tg += aw2 * ca[1];
+					texres.tb += aw2 * ca[2];
+					noiseHelper.cellNoiseV(pa[6], pa[7], pa[8], ca);
+					texres.tr += aw3 * ca[0];
+					texres.tg += aw3 * ca[1];
+					texres.tb += aw3 * ca[2];
+					noiseHelper.cellNoiseV(pa[9], pa[10], pa[11], ca);
+					texres.tr += aw4 * ca[0];
+					texres.tg += aw4 * ca[1];
+					texres.tb += aw4 * ca[2];
+					if (vn_coltype >= 2) {
+						float t1 = (da[1] - da[0]) * 10.0f;
+						if (t1 > 1) {
+							t1 = 1.0f;
+						}
+						if (vn_coltype == 3) {
+							t1 *= texres.tin;
+						} else {
+							t1 *= sc;
+						}
+						texres.tr *= t1;
+						texres.tg *= t1;
+						texres.tb *= t1;
+					} else {
+						texres.tr *= sc;
+						texres.tg *= sc;
+						texres.tb *= sc;
+					}
+				}
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					if (texres.nor != null) {
+						float offs = nabla / noisesize; // also scaling of texvec
+						// calculate bumpnormal
+						noiseHelper.voronoi(texvec[0] + offs, texvec[1], texvec[2], da, pa, vn_mexp, vn_distm);
+						texres.nor[0] = sc * FastMath.abs(vn_w1 * da[0] + vn_w2 * da[1] + vn_w3 * da[2] + vn_w4 * da[3]);
+						noiseHelper.voronoi(texvec[0], texvec[1] + offs, texvec[2], da, pa, vn_mexp, vn_distm);
+						texres.nor[1] = sc * FastMath.abs(vn_w1 * da[0] + vn_w2 * da[1] + vn_w3 * da[2] + vn_w4 * da[3]);
+						noiseHelper.voronoi(texvec[0], texvec[1], texvec[2] + offs, da, pa, vn_mexp, vn_distm);
+						texres.nor[2] = sc * FastMath.abs(vn_w1 * da[0] + vn_w2 * da[1] + vn_w3 * da[2] + vn_w4 * da[3]);
+						noiseHelper.texNormalDerivate(colorBand, texres, dataRepository);
+					}
+				}
+
+				if (vn_coltype != 0 || colorBand != null) {
+					noiseHelper.brightnesAndContrastRGB(tex, texres);
+					data.put((byte) (texres.tr * 255.0f));// tin or tr??
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					noiseHelper.brightnesAndContrast(texres, contrast, brightness);
+					data.put((byte) (texres.tin * 255.0f));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method generates the distorted noise texture.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param width
+	 *            the width of the texture
+	 * @param height
+	 *            the height of the texture
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the generated texture
+	 */
+	protected Texture distnoise(Structure tex, int width, int height, DataRepository dataRepository) {
+		NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class);
+		float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue();
+		float nabla = ((Number) tex.getFieldValue("nabla")).floatValue();
+		float distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue();
+		int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue();
+		int noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue();
+		float contrast = ((Number) tex.getFieldValue("contrast")).floatValue();
+		float brightness = ((Number) tex.getFieldValue("bright")).floatValue();
+
+		TexResult texres = new TexResult();
+		float[] texvec = new float[] { 0, 0, 0 };
+		float wDelta = 1.0f / width, hDelta = 1.0f / height;
+		int halfW = width, halfH = height;
+		width <<= 1;
+		height <<= 1;
+		ColorBand colorBand = this.readColorband(tex, dataRepository);
+		Format format = colorBand != null ? Format.RGB8 : Format.Luminance8;
+		int bytesPerPixel = colorBand != null ? 3 : 1;
+
+		ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
+		for (int i = -halfW; i < halfW; ++i) {
+			texvec[0] = wDelta * i / noisesize;
+			for (int j = -halfH; j < halfH; ++j) {
+				texvec[1] = hDelta * j / noisesize;
+
+				texres.tin = noiseHelper.mgVLNoise(texvec[0], texvec[1], texvec[2], distAmount, noisebasis, noisebasis2);
+				if (colorBand != null) {
+					noiseHelper.doColorband(colorBand, texres, dataRepository);
+					if (texres.nor != null) {
+						float offs = nabla / noisesize; // also scaling of texvec
+						/* calculate bumpnormal */
+						texres.nor[0] = noiseHelper.mgVLNoise(texvec[0] + offs, texvec[1], texvec[2], distAmount, noisebasis, noisebasis2);
+						texres.nor[1] = noiseHelper.mgVLNoise(texvec[0], texvec[1] + offs, texvec[2], distAmount, noisebasis, noisebasis2);
+						texres.nor[2] = noiseHelper.mgVLNoise(texvec[0], texvec[1], texvec[2] + offs, distAmount, noisebasis, noisebasis2);
+						noiseHelper.texNormalDerivate(colorBand, texres, dataRepository);
+					}
+
+					noiseHelper.brightnesAndContrastRGB(tex, texres);
+					data.put((byte) (texres.tr * 255.0f));
+					data.put((byte) (texres.tg * 255.0f));
+					data.put((byte) (texres.tb * 255.0f));
+				} else {
+					noiseHelper.brightnesAndContrast(texres, contrast, brightness);
+					data.put((byte) (texres.tin * 255.0f));
+				}
+			}
+		}
+		return new Texture2D(new Image(format, width, height, data));
+	}
+
+	/**
+	 * This method reads the colorband data from the given texture structure.
+	 * 
+	 * @param tex
+	 *            the texture structure
+	 * @param dataRepository
+	 *            the data repository
+	 * @return read colorband or null if not present
+	 */
+	protected ColorBand readColorband(Structure tex, DataRepository dataRepository) {
+		ColorBand result = null;
+		int flag = ((Number) tex.getFieldValue("flag")).intValue();
+		if ((flag & com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_COLORBAND) != 0) {
+			Pointer pColorband = (Pointer) tex.getFieldValue("coba");
+			Structure colorbandStructure;
+			try {
+				colorbandStructure = pColorband.fetchData(dataRepository.getInputStream()).get(0);
+				result = new ColorBand(colorbandStructure);
+			} catch (BlenderFileException e) {
+				LOGGER.warning("Cannot fetch the colorband structure. The reason: " + e.getLocalizedMessage());
+				// TODO: throw an exception here ???
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * This method blends the given texture with material color and the defined color in 'map to' panel. As a result of this method a new
+	 * texture is created. The input texture is NOT modified.
+	 * 
+	 * @param materialColor
+	 *            the material diffuse color
+	 * @param texture
+	 *            the texture we use in blending
+	 * @param color
+	 *            the color defined for the texture
+	 * @param affectFactor
+	 *            the factor that the color affects the texture (value form 0.0 to 1.0)
+	 * @param blendType
+	 *            the blending type
+	 * @param dataRepository
+	 *            the data repository
+	 * @return new texture that was created after the blending
+	 */
+	public Texture blendTexture(float[] materialColor, Texture texture, float[] color, float affectFactor, 
+			int blendType, boolean neg,
+			DataRepository dataRepository) {
+		Format format = texture.getImage().getFormat();
+		ByteBuffer data = texture.getImage().getData(0);
+		data.rewind();
+		int width = texture.getImage().getWidth();
+		int height = texture.getImage().getHeight();
+		ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 3);
+
+		float[] resultPixel = new float[3];
+		float[] texPixel = new float[3];
+		int dataIndex = 0;
+
+		while (data.hasRemaining()) {
+			byte pixelValue = data.get();
+			texPixel[0] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f;
+			if(neg) {
+				texPixel[0] = 1.0f - texPixel[0];
+			}
+			if (format == Format.ABGR8) {
+				//intensity not used at the moment
+				pixelValue = data.get();
+				texPixel[2] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f;
+				if(neg) {
+					texPixel[2] = 1.0f - texPixel[2];
+				}
+				pixelValue = data.get();
+				texPixel[1] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f;
+				if(neg) {
+					texPixel[1] = 1.0f - texPixel[1];
+				}
+				pixelValue = data.get();
+				texPixel[0] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f;
+				if(neg) {
+					texPixel[0] = 1.0f - texPixel[0];
+				}
+				
+				this.blendPixel(resultPixel, materialColor, texPixel, 1.0f, affectFactor, blendType, dataRepository);
+
+				newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));
+				newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));
+				newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));
+			} else if (format == Format.RGB8) {
+				pixelValue = data.get();
+				texPixel[1] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f;
+				if(neg) {
+					texPixel[1] = 1.0f - texPixel[1];
+				}
+				pixelValue = data.get();
+				texPixel[2] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f;
+				if(neg) {
+					texPixel[2] = 1.0f - texPixel[2];
+				}
+				float tin = texPixel[0];//(texPixel[0] + texPixel[1] + texPixel[2]) / 3.0f;
+				this.blendPixel(resultPixel, texPixel, color, tin, affectFactor, blendType, dataRepository);
+				newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f));
+				newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f));
+				newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f));
+			} else if (format == Format.Luminance8) {
+				this.blendPixel(resultPixel, materialColor, color, texPixel[0], affectFactor, blendType, dataRepository);
+				newData.put((byte) (resultPixel[0] * 255.0f));
+				newData.put((byte) (resultPixel[1] * 255.0f));
+				newData.put((byte) (resultPixel[2] * 255.0f));
+			} else {
+				throw new IllegalStateException("Invalid texture format for blending operation: " + format);
+			}
+		}
+		return new Texture2D(new Image(Format.RGB8, width, height, newData));
+	}
+
+	/**
+	 * This method blends the texture with an appropriate color.
+	 * 
+	 * @param result
+	 *            the result color (variable 'in' in blender source code)
+	 * @param materialColor
+	 *            the texture color (variable 'out' in blender source coude)
+	 * @param color
+	 *            the previous color (variable 'tex' in blender source code)
+	 * @param textureIntensity
+	 *            texture intensity (variable 'fact' in blender source code)
+	 * @param textureFactor
+	 *            texture affection factor (variable 'facg' in blender source code)
+	 * @param blendtype
+	 *            the blend type
+	 * @param dataRepository
+	 *            the data repository
+	 */
+	public void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor,
+			int blendtype, DataRepository dataRepository) {
+		float facm, col;
+
+		switch (blendtype) {
+			case MTEX_BLEND:
+				textureIntensity *= textureFactor;
+				facm = 1.0f - textureIntensity;
+				result[0] = textureIntensity * color[0] + facm * materialColor[0];
+				result[1] = textureIntensity * color[1] + facm * materialColor[1];
+				result[2] = textureIntensity * color[2] + facm * materialColor[2];
+				break;
+			case MTEX_MUL:
+				textureIntensity *= textureFactor;
+				facm = 1.0f - textureFactor;
+				result[0] = (facm + textureIntensity * materialColor[0]) * color[0];
+				result[1] = (facm + textureIntensity * materialColor[1]) * color[1];
+				result[2] = (facm + textureIntensity * materialColor[2]) * color[2];
+				break;
+			case MTEX_DIV:
+				textureIntensity *= textureFactor;
+				facm = 1.0f - textureIntensity;
+				if (color[0] != 0.0) {
+					result[0] = (facm * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f;
+				}
+				if (color[1] != 0.0) {
+					result[1] = (facm * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f;
+				}
+				if (color[2] != 0.0) {
+					result[2] = (facm * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f;
+				}
+				break;
+			case MTEX_SCREEN:
+				textureIntensity *= textureFactor;
+				facm = 1.0f - textureFactor;
+				result[0] = 1.0f - (facm + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]);
+				result[1] = 1.0f - (facm + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]);
+				result[2] = 1.0f - (facm + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]);
+				break;
+			case MTEX_OVERLAY:
+				textureIntensity *= textureFactor;
+				facm = 1.0f - textureFactor;
+				if (materialColor[0] < 0.5f) {
+					result[0] = color[0] * (facm + 2.0f * textureIntensity * materialColor[0]);
+				} else {
+					result[0] = 1.0f - (facm + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]);
+				}
+				if (materialColor[1] < 0.5f) {
+					result[1] = color[1] * (facm + 2.0f * textureIntensity * materialColor[1]);
+				} else {
+					result[1] = 1.0f - (facm + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]);
+				}
+				if (materialColor[2] < 0.5f) {
+					result[2] = color[2] * (facm + 2.0f * textureIntensity * materialColor[2]);
+				} else {
+					result[2] = 1.0f - (facm + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]);
+				}
+				break;
+			case MTEX_SUB:
+				textureIntensity *= textureFactor;
+				result[0] = materialColor[0] - textureIntensity * color[0];
+				result[1] = materialColor[1] - textureIntensity * color[1];
+				result[2] = materialColor[2] - textureIntensity * color[2];
+				result[0] = FastMath.clamp(result[0], 0.0f, 1.0f);
+				result[1] = FastMath.clamp(result[1], 0.0f, 1.0f);
+				result[2] = FastMath.clamp(result[2], 0.0f, 1.0f);
+				break;
+			case MTEX_ADD:
+				textureIntensity *= textureFactor;
+				result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f;
+				result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f;
+				result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f;
+				break;
+			case MTEX_DIFF:
+				textureIntensity *= textureFactor;
+				facm = 1.0f - textureIntensity;
+				result[0] = facm * color[0] + textureIntensity * Math.abs(materialColor[0] - color[0]);
+				result[1] = facm * color[1] + textureIntensity * Math.abs(materialColor[1] - color[1]);
+				result[2] = facm * color[2] + textureIntensity * Math.abs(materialColor[2] - color[2]);
+				break;
+			case MTEX_DARK:
+				textureIntensity *= textureFactor;
+				col = textureIntensity * color[0];
+				result[0] = col < materialColor[0] ? col : materialColor[0];
+				col = textureIntensity * color[1];
+				result[1] = col < materialColor[1] ? col : materialColor[1];
+				col = textureIntensity * color[2];
+				result[2] = col < materialColor[2] ? col : materialColor[2];
+				break;
+			case MTEX_LIGHT:
+				textureIntensity *= textureFactor;
+				col = textureIntensity * color[0];
+				result[0] = col > materialColor[0] ? col : materialColor[0];
+				col = textureIntensity * color[1];
+				result[1] = col > materialColor[1] ? col : materialColor[1];
+				col = textureIntensity * color[2];
+				result[2] = col > materialColor[2] ? col : materialColor[2];
+				break;
+			case MTEX_BLEND_HUE:
+				textureIntensity *= textureFactor;
+				System.arraycopy(materialColor, 0, result, 0, 3);
+				this.rampBlend(MA_RAMP_HUE, result, textureIntensity, color, dataRepository);
+				break;
+			case MTEX_BLEND_SAT:
+				textureIntensity *= textureFactor;
+				System.arraycopy(materialColor, 0, result, 0, 3);
+				this.rampBlend(MA_RAMP_SAT, result, textureIntensity, color, dataRepository);
+				break;
+			case MTEX_BLEND_VAL:
+				textureIntensity *= textureFactor;
+				System.arraycopy(materialColor, 0, result, 0, 3);
+				this.rampBlend(MA_RAMP_VAL, result, textureIntensity, color, dataRepository);
+				break;
+			case MTEX_BLEND_COLOR:
+				textureIntensity *= textureFactor;
+				System.arraycopy(materialColor, 0, result, 0, 3);
+				this.rampBlend(MA_RAMP_COLOR, result, textureIntensity, color, dataRepository);
+				break;
+			default:
+				throw new IllegalStateException("Unknown blend type: " + blendtype);
+		}
+	}
+
+	/**
+	 * The method that performs the ramp blending (whatever it is :P - copied from blender sources).
+	 * 
+	 * @param type
+	 *            the ramp type
+	 * @param rgb
+	 *            the rgb value where the result is stored
+	 * @param fac
+	 *            color affection factor
+	 * @param col
+	 *            the texture color
+	 * @param dataRepository
+	 *            the data repository
+	 */
+	public void rampBlend(int type, float[] rgb, float fac, float[] col, DataRepository dataRepository) {
+		float tmp, facm = 1.0f - fac;
+		MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+
+		switch (type) {
+			case MA_RAMP_HUE:
+				if (rgb.length == 3) {
+					float[] colorTransformResult = new float[3];
+					materialHelper.rgbToHsv(col[0], col[1], col[2], colorTransformResult);
+					if (colorTransformResult[1] != 0.0f) {
+						float colH = colorTransformResult[0];
+						materialHelper.rgbToHsv(rgb[0], rgb[1], rgb[2], colorTransformResult);
+						materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult);
+						rgb[0] = facm * rgb[0] + fac * colorTransformResult[0];
+						rgb[1] = facm * rgb[1] + fac * colorTransformResult[1];
+						rgb[2] = facm * rgb[2] + fac * colorTransformResult[2];
+					}
+				}
+				break;
+			case MA_RAMP_SAT:
+				if (rgb.length == 3) {
+					float[] colorTransformResult = new float[3];
+					materialHelper.rgbToHsv(rgb[0], rgb[1], rgb[2], colorTransformResult);
+					float rH = colorTransformResult[0];
+					float rS = colorTransformResult[1];
+					float rV = colorTransformResult[2];
+					if (rS != 0) {
+						materialHelper.rgbToHsv(col[0], col[1], col[2], colorTransformResult);
+						materialHelper.hsvToRgb(rH, (facm * rS + fac * colorTransformResult[1]), rV, rgb);
+					}
+				}
+				break;
+			case MA_RAMP_VAL:
+				if (rgb.length == 3) {
+					float[] rgbToHsv = new float[3];
+					float[] colToHsv = new float[3];
+					materialHelper.rgbToHsv(rgb[0], rgb[1], rgb[2], rgbToHsv);
+					materialHelper.rgbToHsv(col[0], col[1], col[2], colToHsv);
+					materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], (facm * rgbToHsv[2] + fac * colToHsv[2]), rgb);
+				}
+				break;
+			case MA_RAMP_COLOR:
+				if (rgb.length == 3) {
+					float[] rgbToHsv = new float[3];
+					float[] colToHsv = new float[3];
+					materialHelper.rgbToHsv(col[0], col[1], col[2], colToHsv);
+					if (colToHsv[2] != 0) {
+						materialHelper.rgbToHsv(rgb[0], rgb[1], rgb[2], rgbToHsv);
+						materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv);
+						rgb[0] = facm * rgb[0] + fac * rgbToHsv[0];
+						rgb[1] = facm * rgb[1] + fac * rgbToHsv[1];
+						rgb[2] = facm * rgb[2] + fac * rgbToHsv[2];
+					}
+				}
+				break;
+			case MA_RAMP_BLEND:
+				rgb[0] = facm * rgb[0] + fac * col[0];
+				if (rgb.length == 3) {
+					rgb[1] = facm * rgb[1] + fac * col[1];
+					rgb[2] = facm * rgb[2] + fac * col[2];
+				}
+				break;
+			case MA_RAMP_ADD:
+				rgb[0] += fac * col[0];
+				if (rgb.length == 3) {
+					rgb[1] += fac * col[1];
+					rgb[2] += fac * col[2];
+				}
+				break;
+			case MA_RAMP_MULT:
+				rgb[0] *= facm + fac * col[0];
+				if (rgb.length == 3) {
+					rgb[1] *= facm + fac * col[1];
+					rgb[2] *= facm + fac * col[2];
+				}
+				break;
+			case MA_RAMP_SCREEN:
+				rgb[0] = 1.0f - (facm + fac * (1.0f - col[0])) * (1.0f - rgb[0]);
+				if (rgb.length == 3) {
+					rgb[1] = 1.0f - (facm + fac * (1.0f - col[1])) * (1.0f - rgb[1]);
+					rgb[2] = 1.0f - (facm + fac * (1.0f - col[2])) * (1.0f - rgb[2]);
+				}
+				break;
+			case MA_RAMP_OVERLAY:
+				if (rgb[0] < 0.5f) {
+					rgb[0] *= facm + 2.0f * fac * col[0];
+				} else {
+					rgb[0] = 1.0f - (facm + 2.0f * fac * (1.0f - col[0])) * (1.0f - rgb[0]);
+				}
+				if (rgb.length == 3) {
+					if (rgb[1] < 0.5f) {
+						rgb[1] *= facm + 2.0f * fac * col[1];
+					} else {
+						rgb[1] = 1.0f - (facm + 2.0f * fac * (1.0f - col[1])) * (1.0f - rgb[1]);
+					}
+					if (rgb[2] < 0.5f) {
+						rgb[2] *= facm + 2.0f * fac * col[2];
+					} else {
+						rgb[2] = 1.0f - (facm + 2.0f * fac * (1.0f - col[2])) * (1.0f - rgb[2]);
+					}
+				}
+				break;
+			case MA_RAMP_SUB:
+				rgb[0] -= fac * col[0];
+				if (rgb.length == 3) {
+					rgb[1] -= fac * col[1];
+					rgb[2] -= fac * col[2];
+				}
+				break;
+			case MA_RAMP_DIV:
+				if (col[0] != 0.0) {
+					rgb[0] = facm * rgb[0] + fac * rgb[0] / col[0];
+				}
+				if (rgb.length == 3) {
+					if (col[1] != 0.0) {
+						rgb[1] = facm * rgb[1] + fac * rgb[1] / col[1];
+					}
+					if (col[2] != 0.0) {
+						rgb[2] = facm * rgb[2] + fac * rgb[2] / col[2];
+					}
+				}
+				break;
+			case MA_RAMP_DIFF:
+				rgb[0] = facm * rgb[0] + fac * Math.abs(rgb[0] - col[0]);
+				if (rgb.length == 3) {
+					rgb[1] = facm * rgb[1] + fac * Math.abs(rgb[1] - col[1]);
+					rgb[2] = facm * rgb[2] + fac * Math.abs(rgb[2] - col[2]);
+				}
+				break;
+			case MA_RAMP_DARK:
+				tmp = fac * col[0];
+				if (tmp < rgb[0]) {
+					rgb[0] = tmp;
+				}
+				if (rgb.length == 3) {
+					tmp = fac * col[1];
+					if (tmp < rgb[1]) {
+						rgb[1] = tmp;
+					}
+					tmp = fac * col[2];
+					if (tmp < rgb[2]) {
+						rgb[2] = tmp;
+					}
+				}
+				break;
+			case MA_RAMP_LIGHT:
+				tmp = fac * col[0];
+				if (tmp > rgb[0]) {
+					rgb[0] = tmp;
+				}
+				if (rgb.length == 3) {
+					tmp = fac * col[1];
+					if (tmp > rgb[1]) {
+						rgb[1] = tmp;
+					}
+					tmp = fac * col[2];
+					if (tmp > rgb[2]) {
+						rgb[2] = tmp;
+					}
+				}
+				break;
+			case MA_RAMP_DODGE:
+				if (rgb[0] != 0.0) {
+					tmp = 1.0f - fac * col[0];
+					if (tmp <= 0.0) {
+						rgb[0] = 1.0f;
+					} else if ((tmp = rgb[0] / tmp) > 1.0) {
+						rgb[0] = 1.0f;
+					} else {
+						rgb[0] = tmp;
+					}
+				}
+				if (rgb.length == 3) {
+					if (rgb[1] != 0.0) {
+						tmp = 1.0f - fac * col[1];
+						if (tmp <= 0.0) {
+							rgb[1] = 1.0f;
+						} else if ((tmp = rgb[1] / tmp) > 1.0) {
+							rgb[1] = 1.0f;
+						} else {
+							rgb[1] = tmp;
+						}
+					}
+					if (rgb[2] != 0.0) {
+						tmp = 1.0f - fac * col[2];
+						if (tmp <= 0.0) {
+							rgb[2] = 1.0f;
+						} else if ((tmp = rgb[2] / tmp) > 1.0) {
+							rgb[2] = 1.0f;
+						} else {
+							rgb[2] = tmp;
+						}
+					}
+
+				}
+				break;
+			case MA_RAMP_BURN:
+				tmp = facm + fac * col[0];
+				if (tmp <= 0.0) {
+					rgb[0] = 0.0f;
+				} else if ((tmp = 1.0f - (1.0f - rgb[0]) / tmp) < 0.0) {
+					rgb[0] = 0.0f;
+				} else if (tmp > 1.0) {
+					rgb[0] = 1.0f;
+				} else {
+					rgb[0] = tmp;
+				}
+
+				if (rgb.length == 3) {
+					tmp = facm + fac * col[1];
+					if (tmp <= 0.0) {
+						rgb[1] = 0.0f;
+					} else if ((tmp = 1.0f - (1.0f - rgb[1]) / tmp) < 0.0) {
+						rgb[1] = 0.0f;
+					} else if (tmp > 1.0) {
+						rgb[1] = 1.0f;
+					} else {
+						rgb[1] = tmp;
+					}
+
+					tmp = facm + fac * col[2];
+					if (tmp <= 0.0) {
+						rgb[2] = 0.0f;
+					} else if ((tmp = 1.0f - (1.0f - rgb[2]) / tmp) < 0.0) {
+						rgb[2] = 0.0f;
+					} else if (tmp > 1.0) {
+						rgb[2] = 1.0f;
+					} else {
+						rgb[2] = tmp;
+					}
+				}
+				break;
+			default:
+				throw new IllegalStateException("Unknown ramp type: " + type);
+		}
+	}
+
+	/**
+	 * This class returns a texture read from the file or from packed blender data.
+	 * 
+	 * @param image
+	 *            image structure filled with data
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the texture that can be used by JME engine
+	 * @throws BlenderFileException
+	 *             this exception is thrown when the blend file structure is somehow invalid or corrupted
+	 */
+	public Texture getTextureFromImage(Structure image, DataRepository dataRepository) throws BlenderFileException {
+		Texture result = (Texture) dataRepository.getLoadedFeature(image.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+		if (result == null) {
+			Pointer pPackedFile = (Pointer) image.getFieldValue("packedfile");
+			if (pPackedFile.isNull()) {
+				LOGGER.info("Reading texture from file!");
+				String imagePath = image.getFieldValue("name").toString();
+				result = this.loadTextureFromFile(imagePath, dataRepository);
+			} else {
+				LOGGER.info("Packed texture. Reading directly from the blend file!");
+				Structure packedFile = pPackedFile.fetchData(dataRepository.getInputStream()).get(0);
+				Pointer pData = (Pointer) packedFile.getFieldValue("data");
+				FileBlockHeader dataFileBlock = dataRepository.getFileBlock(pData.getOldMemoryAddress());
+				dataRepository.getInputStream().setPosition(dataFileBlock.getBlockPosition());
+				ImageLoader imageLoader = new ImageLoader();
+
+				// Should the texture be flipped? It works for sinbad ..
+				Image im = imageLoader.loadImage(dataRepository.getInputStream(), dataFileBlock.getBlockPosition(), true);
+				if (im != null) {
+					result = new Texture2D(im);
+				}
+			}
+			if (result != null) {
+				result.setName(String.valueOf(8));// 8 = TEX_IMAGE
+				result.setWrap(Texture.WrapMode.Repeat);
+				dataRepository.addLoadedFeatures(image.getOldMemoryAddress(), image.getName(), image, result);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * This method loads the textre from outside the blend file.
+	 * 
+	 * @param name
+	 *            the path to the image
+	 * @param dataRepository
+	 *            the data repository
+	 * @return the loaded image or null if the image cannot be found
+	 */
+	protected Texture loadTextureFromFile(String name, DataRepository dataRepository) {
+		Image image = null;
+		ImageLoader imageLoader = new ImageLoader();
+		FileInputStream fis = null;
+		ImageType[] imageTypes = ImageType.values();
+		// TODO: would be nice to have the model asset key here to getthe models older in the assetmanager
+
+		if (name.startsWith("//")) {
+			File modelFolder = new File(dataRepository.getBlenderKey().getName());
+			File textureFolder = modelFolder.getParentFile();
+
+			if (textureFolder != null) {
+				name = textureFolder.getPath() + "/." + name.substring(1); // replace the // that means "relative" for blender (hopefully)
+																			// with
+			} else {
+				name = name.substring(1);
+			}
+
+			TextureKey texKey = new TextureKey(name, true);
+			Texture tex = dataRepository.getAssetManager().loadTexture(texKey);
+			image = tex.getImage();
+		}
+
+		// 2. Try using the direct path from the blender file
+		if (image == null) {
+			File textureFile = new File(name);
+			if (textureFile.exists() && textureFile.isFile()) {
+				LOGGER.log(Level.INFO, "Trying with: {0}", name);
+				try {
+					for (int i = 0; i < imageTypes.length && image == null; ++i) {
+						fis = new FileInputStream(textureFile);
+						image = imageLoader.loadImage(fis, imageTypes[i], false);
+						this.closeStream(fis);
+					}
+				} catch (FileNotFoundException e) {
+					assert false : e;// this should NEVER happen
+				} finally {
+					this.closeStream(fis);
+				}
+			}
+		}
+
+		// 3. if 2 failed we start including the parent folder(s) to see if the texture
+		// can be found
+		if (image == null) {
+			String baseName = File.separatorChar != '/' ? name.replace(File.separatorChar, '/') : name;
+			int idx = baseName.lastIndexOf('/');
+			while (idx != -1 && image == null) {
+				String texName = baseName.substring(idx + 1);
+				File textureFile = new File(texName);
+				if (textureFile.exists() && textureFile.isFile()) {
+					LOGGER.info("Trying with: " + texName);
+					try {
+						for (int i = 0; i < imageTypes.length && image == null; ++i) {
+							fis = new FileInputStream(textureFile);
+							image = imageLoader.loadImage(fis, imageTypes[i], false);
+						}
+					} catch (FileNotFoundException e) {
+						assert false : e;// this should NEVER happen
+					} finally {
+						this.closeStream(fis);
+					}
+				}
+				if (idx > 1) {
+					idx = baseName.lastIndexOf('/', idx - 1);
+				} else {
+					idx = -1;
+				}
+			}
+		}
+
+		return image == null ? null : new Texture2D(image);
+	}
+
+	/**
+	 * This method closes the given stream.
+	 * 
+	 * @param is
+	 *            the input stream that is to be closed
+	 */
+	protected void closeStream(InputStream is) {
+		if (is != null) {
+			try {
+				is.close();
+			} catch (IOException e) {
+				LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
+			}
+		}
+	}
+
+	/**
+	 * An image loader class. It uses three loaders (AWTLoader, TGALoader and DDSLoader) in an attempt to load the image from the given
+	 * input stream.
+	 * 
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	protected static class ImageLoader extends AWTLoader {
+		private static final Logger	LOGGER		= Logger.getLogger(ImageLoader.class.getName());
+
+		protected DDSLoader			ddsLoader	= new DDSLoader();									// DirectX image loader
+
+		/**
+		 * This method loads the image from the blender file itself. It tries each loader to load the image.
+		 * 
+		 * @param inputStream
+		 *            blender input stream
+		 * @param startPosition
+		 *            position in the stream where the image data starts
+		 * @param flipY
+		 *            if the image should be flipped (does not work with DirectX image)
+		 * @return loaded image or null if it could not be loaded
+		 */
+		public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) {
+			// loading using AWT loader
+			inputStream.setPosition(startPosition);
+			Image result = this.loadImage(inputStream, ImageType.AWT, flipY);
+			// loading using TGA loader
+			if (result == null) {
+				inputStream.setPosition(startPosition);
+				result = this.loadImage(inputStream, ImageType.TGA, flipY);
+			}
+			// loading using DDS loader
+			if (result == null) {
+				inputStream.setPosition(startPosition);
+				result = this.loadImage(inputStream, ImageType.DDS, flipY);
+			}
+
+			if (result == null) {
+				LOGGER.warning("Image could not be loaded by none of available loaders!");
+			}
+
+			return result;
+		}
+
+		/**
+		 * This method loads an image of a specified type from the given input stream.
+		 * 
+		 * @param inputStream
+		 *            the input stream we read the image from
+		 * @param imageType
+		 *            the type of the image {@link ImageType}
+		 * @param flipY
+		 *            if the image should be flipped (does not work with DirectX image)
+		 * @return loaded image or null if it could not be loaded
+		 */
+		public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) {
+			Image result = null;
+			switch (imageType) {
+				case AWT:
+					try {
+						result = this.load(inputStream, flipY);
+					} catch (Exception e) {
+						LOGGER.info("Unable to load image using AWT loader!");
+					}
+					break;
+				case DDS:
+					try {
+						result = ddsLoader.load(inputStream);
+					} catch (Exception e) {
+						LOGGER.info("Unable to load image using DDS loader!");
+					}
+					break;
+				case TGA:
+					try {
+						result = TGALoader.load(inputStream, flipY);
+					} catch (Exception e) {
+						LOGGER.info("Unable to load image using TGA loader!");
+					}
+					break;
+				default:
+					throw new IllegalStateException("Unknown image type: " + imageType);
+			}
+			return result;
+		}
+	}
+
+	/**
+	 * Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files
+	 * 
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	public static enum ImageType {
+		AWT, TGA, DDS;
+	}
+
+	/**
+	 * The result pixel of generated texture computations;
+	 * 
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	protected static class TexResult implements Cloneable {
+		public float	tin, tr, tg, tb, ta;
+		public int		talpha;
+		public float[]	nor;
+
+		@Override
+		public Object clone() throws CloneNotSupportedException {
+			return super.clone();
+		}
+	}
+
+	/**
+	 * A class constaining the colorband data.
+	 * 
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	protected static class ColorBand {
+		public int		flag, tot, cur, ipotype;
+		public CBData[]	data	= new CBData[32];
+
+		/**
+		 * Constructor. Loads the data from the given structure.
+		 * 
+		 * @param cbdataStructure
+		 *            the colorband structure
+		 */
+		@SuppressWarnings("unchecked")
+		public ColorBand(Structure colorbandStructure) {
+			this.flag = ((Number) colorbandStructure.getFieldValue("flag")).intValue();
+			this.tot = ((Number) colorbandStructure.getFieldValue("tot")).intValue();
+			this.cur = ((Number) colorbandStructure.getFieldValue("cur")).intValue();
+			this.ipotype = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue();
+			DynamicArray<Structure> data = (DynamicArray<Structure>) colorbandStructure.getFieldValue("data");
+			for (int i = 0; i < data.getTotalSize(); ++i) {
+				this.data[i] = new CBData(data.get(i));
+			}
+		}
+	}
+
+	/**
+	 * Class to store the single colorband unit data.
+	 * 
+	 * @author Marcin Roguski (Kaelthas)
+	 */
+	protected static class CBData implements Cloneable {
+		public float	r, g, b, a, pos;
+		public int		cur;
+
+		/**
+		 * Constructor. Loads the data from the given structure.
+		 * 
+		 * @param cbdataStructure
+		 *            the structure containing the CBData object
+		 */
+		public CBData(Structure cbdataStructure) {
+			this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue();
+			this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue();
+			this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue();
+			this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue();
+			this.pos = ((Number) cbdataStructure.getFieldValue("pos")).floatValue();
+			this.cur = ((Number) cbdataStructure.getFieldValue("cur")).intValue();
+		}
+
+		@Override
+		public Object clone() throws CloneNotSupportedException {
+			return super.clone();
+		}
+	}
+	
+	public static class GeneratedTextureData {
+		public ByteBuffer luminanceData;
+		public ByteBuffer rgbData;
+		public Format rgbFormat;
+		public int width;
+		public int height;
+		
+		public GeneratedTextureData(ByteBuffer luminanceData, ByteBuffer rgbData, Format rgbFormat, int width, int height) {
+			this.luminanceData = luminanceData;
+			this.rgbData = rgbData;
+			this.rgbFormat = rgbFormat;
+			this.width = width;
+			this.height = height;
+		}
+	}
+}

BIN
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/noiseconstants.dat


+ 225 - 0
engine/src/blender/com/jme3/scene/plugins/blender/structures/AbstractInfluenceFunction.java

@@ -0,0 +1,225 @@
+package com.jme3.scene.plugins.blender.structures;
+
+import java.util.logging.Logger;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneAnimation;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.structures.Constraint.Space;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+/**
+ * This class is used to calculate the constraint. The following methods should be implemented: affectLocation,
+ * affectRotation and affectScale. This class also defines all constants required by known deriving classes.
+ * @author Marcin Roguski
+ */
+public abstract class AbstractInfluenceFunction {
+	protected static final Logger	LOGGER				= Logger.getLogger(AbstractInfluenceFunction.class.getName());
+
+	protected static final float	IK_SOLVER_ERROR		= 0.5f;
+
+	//DISTLIMIT
+	protected static final int		LIMITDIST_INSIDE	= 0;
+	protected static final int		LIMITDIST_OUTSIDE	= 1;
+	protected static final int		LIMITDIST_ONSURFACE	= 2;
+
+	//CONSTRAINT_TYPE_LOCLIKE
+	protected static final int		LOCLIKE_X			= 0x01;
+	protected static final int		LOCLIKE_Y			= 0x02;
+	protected static final int		LOCLIKE_Z			= 0x04;
+
+	//ROTLIKE
+	protected static final int		ROTLIKE_X			= 0x01;
+	protected static final int		ROTLIKE_Y			= 0x02;
+	protected static final int		ROTLIKE_Z			= 0x04;
+	protected static final int		ROTLIKE_X_INVERT	= 0x10;
+	protected static final int		ROTLIKE_Y_INVERT	= 0x20;
+	protected static final int		ROTLIKE_Z_INVERT	= 0x40;
+	protected static final int		ROTLIKE_OFFSET		= 0x80;
+
+	//SIZELIKE
+	protected static final int		SIZELIKE_X			= 0x01;
+	protected static final int		SIZELIKE_Y			= 0x02;
+	protected static final int		SIZELIKE_Z			= 0x04;
+	protected static final int		SIZELIKE_OFFSET		= 0x80;
+
+	/* LOCLIKE_TIP is a depreceated option... use headtail=1.0f instead */
+	//protected static final int LOCLIKE_TIP = 0x08;
+	protected static final int		LOCLIKE_X_INVERT	= 0x10;
+	protected static final int		LOCLIKE_Y_INVERT	= 0x20;
+	protected static final int		LOCLIKE_Z_INVERT	= 0x40;
+	protected static final int		LOCLIKE_OFFSET		= 0x80;
+
+	//LOCLIMIT, SIZELIMIT
+	protected static final int		LIMIT_XMIN			= 0x01;
+	protected static final int		LIMIT_XMAX			= 0x02;
+	protected static final int		LIMIT_YMIN			= 0x04;
+	protected static final int		LIMIT_YMAX			= 0x08;
+	protected static final int		LIMIT_ZMIN			= 0x10;
+	protected static final int		LIMIT_ZMAX			= 0x20;
+
+	//ROTLIMIT
+	protected static final int		LIMIT_XROT			= 0x01;
+	protected static final int		LIMIT_YROT			= 0x02;
+	protected static final int		LIMIT_ZROT			= 0x04;
+
+	/** The type of the constraint. */
+	protected ConstraintType		constraintType;
+	/** The data repository. */
+	protected DataRepository		dataRepository;
+
+	/**
+	 * Constructor.
+	 * @param constraintType
+	 *        the type of the current constraint
+	 * @param dataRepository
+	 *        the data repository
+	 */
+	public AbstractInfluenceFunction(ConstraintType constraintType, DataRepository dataRepository) {
+		this.constraintType = constraintType;
+		this.dataRepository = dataRepository;
+	}
+
+	/**
+	 * This method validates the constraint type. It throws an IllegalArgumentException if the constraint type of the
+	 * given structure is invalid.
+	 * @param constraintStructure
+	 *        the structure with constraint data
+	 */
+	protected void validateConstraintType(Structure constraintStructure) {
+		if(!constraintType.getClassName().equalsIgnoreCase(constraintStructure.getType())) {
+			throw new IllegalArgumentException("Invalud structure type (" + constraintStructure.getType() + ") for the constraint: " + constraintType.getClassName() + '!');
+		}
+	}
+
+	/**
+	 * This method affects the bone animation tracks for the given skeleton.
+	 * @param skeleton
+	 *        the skeleton containing the affected bones by constraint
+	 * @param boneAnimation
+	 *        the bone animation baked traces
+	 * @param constraint
+	 *        the constraint
+	 */
+	public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {}
+
+	/**
+	 * This method returns the bone traces for the bone that is affected by the given constraint.
+	 * @param skeleton
+	 *        the skeleton containing bones
+	 * @param boneAnimation
+	 *        the bone animation that affects the skeleton
+	 * @param constraint
+	 *        the affecting constraint
+	 * @return the bone track for the bone that is being affected by the constraint
+	 */
+	protected BoneTrack getBoneTrack(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {
+		Long boneOMA = constraint.getBoneOMA();
+		Bone bone = (Bone)dataRepository.getLoadedFeature(boneOMA, LoadedFeatureDataType.LOADED_FEATURE);
+		int boneIndex = skeleton.getBoneIndex(bone);
+		if(boneIndex != -1) {
+			//searching for track for this bone
+			for(BoneTrack boneTrack : boneAnimation.getTracks()) {
+				if(boneTrack.getTargetBoneIndex() == boneIndex) {
+					return boneTrack;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * This method returns the target or subtarget object (if specified).
+	 * @param constraint
+	 *        the constraint instance
+	 * @return target or subtarget feature
+	 */
+	protected Object getTarget(Constraint constraint, LoadedFeatureDataType loadedFeatureDataType) {
+		Long targetOMA = ((Pointer)constraint.getData().getFieldValue("tar")).getOldMemoryAddress();
+		Object targetObject = dataRepository.getLoadedFeature(targetOMA, loadedFeatureDataType);
+		String subtargetName = constraint.getData().getFieldValue("subtarget").toString();
+		if(subtargetName.length() > 0) {
+			return dataRepository.getLoadedFeature(subtargetName, loadedFeatureDataType);
+		}
+		return targetObject;
+	}
+
+	/**
+	 * This method returns target's object location.
+	 * @param constraint
+	 *        the constraint instance
+	 * @return target's object location
+	 */
+	protected Vector3f getTargetLocation(Constraint constraint) {
+		Long targetOMA = ((Pointer)constraint.getData().getFieldValue("tar")).getOldMemoryAddress();
+		Space targetSpace = constraint.getTargetSpace();
+		Node targetObject = (Node)dataRepository.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
+		switch(targetSpace) {
+			case CONSTRAINT_SPACE_LOCAL:
+				return targetObject.getLocalTranslation();
+			case CONSTRAINT_SPACE_WORLD:
+				return targetObject.getWorldTranslation();
+			default:
+				throw new IllegalStateException("Invalid space type for target object: " + targetSpace.toString());
+		}
+	}
+	
+	/**
+	 * This method returns target's object location in the specified frame.
+	 * @param constraint
+	 *        the constraint instance
+	 * @param frame
+	 *        the frame number
+	 * @return target's object location
+	 */
+	protected Vector3f getTargetLocation(Constraint constraint, int frame) {
+		return this.getTargetLocation(constraint);//TODO: implement getting location in a specified frame
+	}
+
+	/**
+	 * This method returns target's object rotation.
+	 * @param constraint
+	 *        the constraint instance
+	 * @return target's object rotation
+	 */
+	protected Quaternion getTargetRotation(Constraint constraint) {
+		Long targetOMA = ((Pointer)constraint.getData().getFieldValue("tar")).getOldMemoryAddress();
+		Space targetSpace = constraint.getTargetSpace();
+		Node targetObject = (Node)dataRepository.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
+		switch(targetSpace) {
+			case CONSTRAINT_SPACE_LOCAL:
+				return targetObject.getLocalRotation();
+			case CONSTRAINT_SPACE_WORLD:
+				return targetObject.getWorldRotation();
+			default:
+				throw new IllegalStateException("Invalid space type for target object: " + targetSpace.toString());
+		}
+	}
+
+	/**
+	 * This method returns target's object scale.
+	 * @param constraint
+	 *        the constraint instance
+	 * @return target's object scale
+	 */
+	protected Vector3f getTargetScale(Constraint constraint) {
+		Long targetOMA = ((Pointer)constraint.getData().getFieldValue("tar")).getOldMemoryAddress();
+		Space targetSpace = constraint.getTargetSpace();
+		Node targetObject = (Node)dataRepository.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
+		switch(targetSpace) {
+			case CONSTRAINT_SPACE_LOCAL:
+				return targetObject.getLocalScale();
+			case CONSTRAINT_SPACE_WORLD:
+				return targetObject.getWorldScale();
+			default:
+				throw new IllegalStateException("Invalid space type for target object: " + targetSpace.toString());
+		}
+	}
+}

+ 137 - 0
engine/src/blender/com/jme3/scene/plugins/blender/structures/BezierCurve.java

@@ -0,0 +1,137 @@
+package com.jme3.scene.plugins.blender.structures;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.utils.DynamicArray;
+
+/**
+ * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize
+ * floating point operations errors.
+ * @author Marcin Roguski
+ */
+public class BezierCurve {
+	public static final int X_VALUE = 0;
+	public static final int	Y_VALUE	= 1;
+	public static final int Z_VALUE = 2;
+
+	/** 
+	 * The type of the curve. Describes the data it modifies. 
+	 * Used in ipos calculations.
+	 */
+	private int				type;
+	/** The dimension of the curve. */
+	private int 			dimension;
+	/** A table of the bezier points. */
+	private float[][][]	bezierPoints;
+
+	@SuppressWarnings("unchecked")
+	public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) {
+		if(dimension != 2 && dimension != 3) {
+			throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!");
+		}
+		this.type = type;
+		this.dimension = dimension;
+		//first index of the bezierPoints table has the length of triples amount
+		//the second index points to a table od three points of a bezier triple (handle, point, handle)
+		//the third index specifies the coordinates of the specific point in a bezier triple
+		bezierPoints = new float[bezTriples.size()][3][dimension];
+		int i = 0, j, k;
+		for(Structure bezTriple : bezTriples) {
+			DynamicArray<Number> vec = (DynamicArray<Number>)bezTriple.getFieldValue("vec");
+			for(j = 0; j < 3; ++j) {
+				for(k = 0; k < dimension; ++k) {
+					bezierPoints[i][j][k] = vec.get(j, k).floatValue();
+				}
+			}
+			++i;
+		}
+	}
+
+	/**
+	 * This method evaluates the data for the specified frame. The Y value is returned.
+	 * @param frame
+	 *        the frame for which the value is being calculated
+	 * @param valuePart
+	 *        this param specifies wheather we should return the X, Y or Z part of the result value; it should have
+	 *        one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result
+	 *        Z_VALUE - the Z factor of the result
+	 * @return the value of the curve
+	 */
+	public float evaluate(int frame, int valuePart) {
+		for(int i = 0; i < bezierPoints.length - 1; ++i) {
+			if(frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) {
+				float t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]);
+				float oneMinusT = 1.0f - t;
+				float oneMinusT2 = oneMinusT * oneMinusT;
+				float t2 = t * t;
+				return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t;
+			}
+		}
+		if(frame < bezierPoints[0][1][0]) {
+			return bezierPoints[0][1][1];
+		} else { //frame>bezierPoints[bezierPoints.length-1][1][0]
+			return bezierPoints[bezierPoints.length - 1][1][1];
+		}
+	}
+
+	/**
+	 * This method returns the frame where last bezier triple center point of the bezier curve is located.
+	 * @return the frame number of the last defined bezier triple point for the curve
+	 */
+	public int getLastFrame() {
+		return (int)bezierPoints[bezierPoints.length - 1][1][0];
+	}
+	
+	/**
+	 * This method returns the type of the bezier curve. The type describes the parameter that this curve modifies
+	 * (ie. LocationX or rotationW of the feature).
+	 * @return the type of the bezier curve
+	 */
+	public int getType() {
+		return type;
+	}
+	
+	/**
+	 * This method returns a list of control points for this curve.
+	 * @return a list of control points for this curve.
+	 */
+	public List<Vector3f> getControlPoints() {
+		List<Vector3f> controlPoints = new ArrayList<Vector3f>(bezierPoints.length * 3);
+		for(int i = 0;i<bezierPoints.length;++i) {
+			controlPoints.add(new Vector3f(bezierPoints[i][0][0], bezierPoints[i][0][1], bezierPoints[i][0][2]));
+			controlPoints.add(new Vector3f(bezierPoints[i][1][0], bezierPoints[i][1][1], bezierPoints[i][1][2]));
+			controlPoints.add(new Vector3f(bezierPoints[i][2][0], bezierPoints[i][2][1], bezierPoints[i][2][2]));
+		}
+		return controlPoints;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder("Bezier curve: ").append(type).append('\n');
+		for(int i = 0; i < bezierPoints.length; ++i) {
+			sb.append(this.toStringBezTriple(i)).append('\n');
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * This method converts the bezier triple of a specified index into text.
+	 * @param tripleIndex
+	 *        index of the triple
+	 * @return text representation of the triple
+	 */
+	private String toStringBezTriple(int tripleIndex) {
+		if(this.dimension==2) {
+			return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" 
+						+ bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" 
+						+ bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]";
+		} else {
+			return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" 
+						+ bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" 
+						+ bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]";
+		}
+	}
+}

+ 162 - 0
engine/src/blender/com/jme3/scene/plugins/blender/structures/Constraint.java

@@ -0,0 +1,162 @@
+package com.jme3.scene.plugins.blender.structures;
+
+import com.jme3.animation.BoneAnimation;
+import com.jme3.animation.Skeleton;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.utils.DataRepository;
+import com.jme3.scene.plugins.blender.utils.Pointer;
+
+/**
+ * The implementation of a constraint.
+ * @author Marcin Roguski
+ */
+public class Constraint {
+	/** The type of this constraint. */
+	private final ConstraintType			type;
+	/** The name of this constraint. */
+	private final String					name;
+	/** The old memory address of the constraint's owner. */
+	private final Long						boneOMA;
+
+	private final Space						ownerSpace;
+
+	private final Space						targetSpace;
+	/** The structure with constraint's data. */
+	private final Structure					data;
+	/** The ipo object defining influence. */
+	private final Ipo						ipo;
+	/** The influence function of this constraint. */
+	private final AbstractInfluenceFunction	influenceFunction;
+
+	/**
+	 * This constructor creates the constraint instance.
+	 * @param constraintStructure
+	 *        the constraint's structure (bConstraint clss in blender 2.49).
+	 * @param influenceFunction
+	 *        the constraint's influence function (taken from ConstraintHelper)
+	 * @param boneOMA
+	 *        the old memory address of the constraint owner
+	 * @param influenceIpo
+	 *        the ipo curve of the influence factor
+	 * @param dataRepository
+	 *        the data repository
+	 * @throws BlenderFileException
+	 */
+	public Constraint(Structure constraintStructure, AbstractInfluenceFunction influenceFunction, Long boneOMA, Space ownerSpace, Space targetSpace, Ipo influenceIpo, DataRepository dataRepository) throws BlenderFileException {
+		if(influenceFunction == null) {
+			throw new IllegalArgumentException("Influence function is not defined!");
+		}
+		Pointer pData = (Pointer)constraintStructure.getFieldValue("data");
+		if(!pData.isNull()) {
+			data = pData.fetchData(dataRepository.getInputStream()).get(0);
+		} else {
+			throw new BlenderFileException("The constraint has no data specified!");
+		}
+		this.boneOMA = boneOMA;
+		this.type = ConstraintType.valueOf(((Number)constraintStructure.getFieldValue("type")).intValue());
+		this.name = constraintStructure.getFieldValue("name").toString();
+		this.ownerSpace = ownerSpace;
+		this.targetSpace = targetSpace;
+		this.ipo = influenceIpo;
+		this.influenceFunction = influenceFunction;
+	}
+
+	/**
+	 * This method returns the name of the constraint.
+	 * @return the name of the constraint
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * This method returns the old memoty address of the bone this constraint affects.
+	 * @return the old memory address of the bone this constraint affects
+	 */
+	public Long getBoneOMA() {
+		return boneOMA;
+	}
+
+	/**
+	 * This method returns owner's transform space.
+	 * @return owner's transform space
+	 */
+	public Space getOwnerSpace() {
+		return ownerSpace;
+	}
+
+	/**
+	 * This method returns target's transform space.
+	 * @return target's transform space
+	 */
+	public Space getTargetSpace() {
+		return targetSpace;
+	}
+
+	/**
+	 * This method returns the type of the constraint.
+	 * @return the type of the constraint
+	 */
+	public ConstraintType getType() {
+		return type;
+	}
+
+	/**
+	 * This method returns the constraint's data structure.
+	 * @return the constraint's data structure
+	 */
+	public Structure getData() {
+		return data;
+	}
+
+	/**
+	 * This method returns the constraint's influcence curve.
+	 * @return the constraint's influcence curve
+	 */
+	public Ipo getIpo() {
+		return ipo;
+	}
+
+	/**
+	 * This method affects the bone animation tracks for the given skeleton.
+	 * @param skeleton
+	 *        the skeleton containing the affected bones by constraint
+	 * @param boneAnimation
+	 *        the bone animation baked traces
+	 * @param constraint
+	 *        the constraint
+	 */
+	public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation) {
+		influenceFunction.affectAnimation(skeleton, boneAnimation, this);
+	}
+
+	/**
+	 * The space of target or owner transformation.
+	 * @author Marcin Roguski
+	 */
+	public static enum Space {
+		CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID;
+
+		/**
+		 * This method returns the enum instance when given the appropriate value from the blend file.
+		 * @param c
+		 *        the blender's value of the space modifier
+		 * @return the scape enum instance
+		 */
+		public static Space valueOf(byte c) {
+			switch(c) {
+				case 0:
+					return CONSTRAINT_SPACE_WORLD;
+				case 1:
+					return CONSTRAINT_SPACE_LOCAL;
+				case 2:
+					return CONSTRAINT_SPACE_POSE;
+				case 3:
+					return CONSTRAINT_SPACE_PARLOCAL;
+				default:
+					return CONSTRAINT_SPACE_INVALID;
+			}
+		}
+	}
+}

+ 143 - 0
engine/src/blender/com/jme3/scene/plugins/blender/structures/ConstraintType.java

@@ -0,0 +1,143 @@
+package com.jme3.scene.plugins.blender.structures;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Constraint types. Definitions taken from blender sources, file: DNA_constraint_types.h. Constraint id's the same as
+ * used in blender. The constraints might have duplicated type ids, depending on the blender version. The purpose of
+ * this enum is to combine class name and the constraint type (id).
+ * @author Marcin Roguski
+ */
+public enum ConstraintType {
+	/* Invalid/legacy constraint */
+	CONSTRAINT_TYPE_NULL(0, "bNullConstraint"),
+	/* Unimplemented non longer :) - during constraints recode, Aligorith */
+	CONSTRAINT_TYPE_CHILDOF(1, "bChildOfConstraint"), 
+	CONSTRAINT_TYPE_KINEMATIC(3, "bKinematicConstraint"), 
+	CONSTRAINT_TYPE_FOLLOWPATH(4, "bFollowPathConstraint"),
+	/* Unimplemented no longer :) - Aligorith */
+	CONSTRAINT_TYPE_ROTLIMIT(5, "bRotLimitConstraint"),
+	/* Unimplemented no longer :) - Aligorith */
+	CONSTRAINT_TYPE_LOCLIMIT(6, "bLocLimitConstraint"),
+	/* Unimplemented no longer :) - Aligorith */
+	CONSTRAINT_TYPE_SIZELIMIT(7, "bSizeLimitConstraint"), 
+	CONSTRAINT_TYPE_ROTLIKE(8, "bRotateLikeConstraint"), 
+	CONSTRAINT_TYPE_LOCLIKE(9, "bLocateLikeConstraint"), 
+	CONSTRAINT_TYPE_SIZELIKE(10, "bSizeLikeConstraint"),
+	/* Unimplemented no longer :) - Aligorith. Scripts */
+	CONSTRAINT_TYPE_PYTHON(11, "bPythonConstraint"), 
+	CONSTRAINT_TYPE_ACTION(12, "bActionConstraint"),
+	/* New Tracking constraint that locks an axis in place - theeth */
+	CONSTRAINT_TYPE_LOCKTRACK(13, "bLockTrackConstraint"),
+	/* limit distance */
+	CONSTRAINT_TYPE_DISTLIMIT(14, "bDistLimitConstraint"),
+	/* claiming this to be mine :) is in tuhopuu bjornmose */
+	CONSTRAINT_TYPE_STRETCHTO(15, "bStretchToConstraint"),
+	/* floor constraint */
+	CONSTRAINT_TYPE_MINMAX(16, "bMinMaxConstraint"),
+	/* rigidbody constraint */
+	CONSTRAINT_TYPE_RIGIDBODYJOINT(17, "bRigidBodyConstraint"),
+	/* clampto constraint */
+	CONSTRAINT_TYPE_CLAMPTO(18, "bClampToConstraint"),
+	/* transformation (loc/rot/size -> loc/rot/size) constraint */
+	CONSTRAINT_TYPE_TRANSFORM(19, "bTransformConstraint"),
+	/* shrinkwrap (loc/rot) constraint */
+	CONSTRAINT_TYPE_SHRINKWRAP(20, "bShrinkwrapConstraint");
+
+	/** The constraint's id (in blender known as 'type'). */
+	private int									constraintId;
+	/** The name of constraint class used by blender. */
+	private String								className;
+	/** The map containing class names and types of constraints. */
+	private static Map<String, ConstraintType>	typesMap	= new HashMap<String, ConstraintType>(ConstraintType.values().length);
+	/** The map containing class names and types of constraints. */
+	private static Map<Integer, ConstraintType>	idsMap	= new HashMap<Integer, ConstraintType>(ConstraintType.values().length);
+	static {
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_NULL.constraintId), CONSTRAINT_TYPE_NULL);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_CHILDOF.constraintId), CONSTRAINT_TYPE_CHILDOF);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_KINEMATIC.constraintId), CONSTRAINT_TYPE_KINEMATIC);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_FOLLOWPATH.constraintId), CONSTRAINT_TYPE_FOLLOWPATH);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ROTLIMIT.constraintId), CONSTRAINT_TYPE_ROTLIMIT);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCLIMIT.constraintId), CONSTRAINT_TYPE_LOCLIMIT);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SIZELIMIT.constraintId), CONSTRAINT_TYPE_SIZELIMIT);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ROTLIKE.constraintId), CONSTRAINT_TYPE_ROTLIKE);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCLIKE.constraintId), CONSTRAINT_TYPE_LOCLIKE);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SIZELIKE.constraintId), CONSTRAINT_TYPE_SIZELIKE);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_PYTHON.constraintId), CONSTRAINT_TYPE_PYTHON);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ACTION.constraintId), CONSTRAINT_TYPE_ACTION);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCKTRACK.constraintId), CONSTRAINT_TYPE_LOCKTRACK);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_DISTLIMIT.constraintId), CONSTRAINT_TYPE_DISTLIMIT);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_STRETCHTO.constraintId), CONSTRAINT_TYPE_STRETCHTO);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_MINMAX.constraintId), CONSTRAINT_TYPE_MINMAX);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_RIGIDBODYJOINT.constraintId), CONSTRAINT_TYPE_RIGIDBODYJOINT);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_CLAMPTO.constraintId), CONSTRAINT_TYPE_CLAMPTO);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_TRANSFORM.constraintId), CONSTRAINT_TYPE_TRANSFORM);
+		idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SHRINKWRAP.constraintId), CONSTRAINT_TYPE_SHRINKWRAP);
+	}
+	/**
+	 * Constructor. Stores constraint type and class name.
+	 * @param constraintId
+	 *        the constraint's type
+	 * @param className
+	 *        the constraint's type name
+	 */
+	private ConstraintType(int constraintId, String className) {
+		this.constraintId = constraintId;
+		this.className = className;
+	}
+
+	/**
+	 * This method returns the type by given constraint id.
+	 * @param constraintId
+	 *        the id of the constraint
+	 * @return the constraint type enum value
+	 */
+	public static ConstraintType valueOf(int constraintId) {
+		return idsMap.get(Integer.valueOf(constraintId));
+	}
+
+	/**
+	 * This method returns the constraint's id (type).
+	 * @return the constraint's id (type)
+	 */
+	public int getConstraintId() {
+		return constraintId;
+	}
+
+	/**
+	 * This method returns the constraint's class name.
+	 * @return the constraint's class name
+	 */
+	public String getClassName() {
+		return className;
+	}
+
+	/**
+	 * This method returns constraint enum type by the given class name.
+	 * @param className
+	 *        the blender's constraint class name
+	 * @return the constraint enum type of the specified class name
+	 */
+	public static ConstraintType getByBlenderClassName(String className) {
+		ConstraintType result = typesMap.get(className);
+		if(result == null) {
+			ConstraintType[] constraints = ConstraintType.values();
+			for(ConstraintType constraint : constraints) {
+				if(constraint.className.equals(className)) {
+					return constraint;
+				}
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * This method returns the type value of the last defined constraint. It can be used for allocating tables for
+	 * storing constraint procedures since not all type values from 0 to the last value are used.
+	 * @return the type value of the last defined constraint
+	 */
+	public static int getLastDefinedTypeValue() {
+		return CONSTRAINT_TYPE_SHRINKWRAP.getConstraintId();
+	}
+}

+ 176 - 0
engine/src/blender/com/jme3/scene/plugins/blender/structures/Ipo.java

@@ -0,0 +1,176 @@
+package com.jme3.scene.plugins.blender.structures;
+
+import com.jme3.animation.BoneTrack;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+
+/**
+ * This class is used to calculate bezier curves value for the given frames. The Ipo (interpolation object) consists
+ * of several b-spline curves (connected 3rd degree bezier curves) of a different type.
+ * @author Marcin Roguski
+ */
+public class Ipo {
+	public static final int AC_LOC_X = 1;
+	public static final int AC_LOC_Y = 2;
+	public static final int AC_LOC_Z = 3;
+	public static final int OB_ROT_X = 7;
+	public static final int OB_ROT_Y = 8;
+	public static final int OB_ROT_Z = 9;
+	public static final int AC_SIZE_X = 13;
+	public static final int AC_SIZE_Y = 14;
+	public static final int AC_SIZE_Z = 15;
+	public static final int AC_QUAT_W = 25;
+	public static final int AC_QUAT_X = 26;
+	public static final int AC_QUAT_Y = 27;
+	public static final int AC_QUAT_Z = 28;
+	
+	/** A list of bezier curves for this interpolation object. */
+	private BezierCurve[]	bezierCurves;
+	/** Each ipo contains one bone track. */
+	private BoneTrack calculatedTrack;
+
+	/**
+	 * Constructor. Stores the bezier curves.
+	 * @param bezierCurves
+	 *        a table of bezier curves
+	 */
+	public Ipo(BezierCurve[] bezierCurves) {
+		this.bezierCurves = bezierCurves;
+	}
+
+	/**
+	 * This method calculates the ipo value for the first curve.
+	 * @param frame
+	 *        the frame for which the value is calculated
+	 * @return calculated ipo value
+	 */
+	public float calculateValue(int frame) {
+		return this.calculateValue(frame, 0);
+	}
+
+	/**
+	 * This method calculates the ipo value for the curve of the specified index. Make sure you do not exceed the
+	 * curves amount. Alway chech the amount of curves before calling this method.
+	 * @param frame
+	 *        the frame for which the value is calculated
+	 * @param curveIndex
+	 *        the index of the curve
+	 * @return calculated ipo value
+	 */
+	public float calculateValue(int frame, int curveIndex) {
+		return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE);
+	}
+
+	/**
+	 * This method returns the curves amount.
+	 * @return the curves amount
+	 */
+	public int getCurvesAmount() {
+		return bezierCurves.length;
+	}
+
+	/**
+	 * This method returns the frame where last bezier triple center point of the specified bezier curve is located.
+	 * @return the frame number of the last defined bezier triple point for the specified ipo
+	 */
+	public int getLastFrame() {
+		int result = 1;
+		for(int i = 0; i < bezierCurves.length; ++i) {
+			int tempResult = bezierCurves[i].getLastFrame();
+			if(tempResult > result) {
+				result = tempResult;
+			}
+		}
+		return result;
+	}
+	
+	public void modifyTranslation(int frame, Vector3f translation) {
+		if(calculatedTrack!=null) {
+			calculatedTrack.getTranslations()[frame].set(translation);
+		}
+	}
+	
+	public void modifyRotation(int frame, Quaternion rotation) {
+		if(calculatedTrack!=null) {
+			calculatedTrack.getRotations()[frame].set(rotation);
+		}
+	}
+	
+	public void modifyScale(int frame, Vector3f scale) {
+		if(calculatedTrack!=null) {
+			calculatedTrack.getScales()[frame].set(scale);
+		}
+	}
+	
+	/**
+	 * This method calculates the value of the curves as a bone track between the specified frames.
+	 * @param boneIndex
+	 *        the index of the bone for which the method calculates the tracks
+	 * @param startFrame
+	 *        the firs frame of tracks (inclusive)
+	 * @param stopFrame
+	 *        the last frame of the tracks (inclusive)
+	 * @param fps
+	 *        frame rate (frames per second)
+	 * @return bone track for the specified bone
+	 */
+	public BoneTrack calculateTrack(int boneIndex, int startFrame, int stopFrame, int fps) {
+		//preparing data for track
+		int framesAmount = stopFrame - startFrame;
+		float start = (startFrame - 1.0f) / fps;
+		float timeBetweenFrames = 1.0f / fps;
+
+		float[] times = new float[framesAmount + 1];
+		Vector3f[] translations = new Vector3f[framesAmount + 1];
+		float[] translation = new float[3];
+		Quaternion[] rotations = new Quaternion[framesAmount + 1];
+		float[] quaternionRotation = new float[4];
+		float[] objectRotation = new float[3];
+		boolean bObjectRotation = false;
+		Vector3f[] scales = new Vector3f[framesAmount + 1];
+		float[] scale = new float[3];
+
+		//calculating track data
+		for(int frame = startFrame; frame <= stopFrame; ++frame) {
+			int index = frame - startFrame;
+			times[index] = start + (frame - 1) * timeBetweenFrames;
+			for(int j = 0; j < bezierCurves.length; ++j) {
+				double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
+				switch(bezierCurves[j].getType()) {
+					case AC_LOC_X:
+					case AC_LOC_Y:
+					case AC_LOC_Z:
+						translation[bezierCurves[j].getType() - 1] = (float)value;
+						break;
+					case OB_ROT_X:
+					case OB_ROT_Y:
+					case OB_ROT_Z:
+						objectRotation[bezierCurves[j].getType() - 7] = (float)value;
+						bObjectRotation = true;
+						break;
+					case AC_SIZE_X:
+					case AC_SIZE_Y:
+					case AC_SIZE_Z:
+						scale[bezierCurves[j].getType() - 13] = (float)value;
+						break;
+					case AC_QUAT_W:
+						quaternionRotation[3] = (float)value;
+						break;
+					case AC_QUAT_X:
+					case AC_QUAT_Y:
+					case AC_QUAT_Z:
+						quaternionRotation[bezierCurves[j].getType() - 26] = (float)value;
+						break;
+					default:
+						//TODO: error? info? warning?
+				}
+			}
+			translations[index] = new Vector3f(translation[0], translation[1], translation[2]);
+			rotations[index] = bObjectRotation ? new Quaternion().fromAngles(objectRotation) : 
+								new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
+			scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
+		}
+		calculatedTrack = new BoneTrack(boneIndex, times, translations, rotations, scales);
+		return calculatedTrack;
+	}
+}

+ 56 - 0
engine/src/blender/com/jme3/scene/plugins/blender/structures/Modifier.java

@@ -0,0 +1,56 @@
+package com.jme3.scene.plugins.blender.structures;
+
+/**
+ * This class represents an object's modifier. The modifier object can be varied and the user needs to know what is
+ * the type of it for the specified type name. For example "ArmatureModifierData" type specified in blender is
+ * represented by AnimData object from jMonkeyEngine.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class Modifier {
+	public static final String	ARRAY_MODIFIER_DATA		= "ArrayModifierData";
+	public static final String	ARMATURE_MODIFIER_DATA	= "ArmatureModifierData";
+	public static final String	PARTICLE_MODIFIER_DATA	= "ParticleSystemModifierData";
+
+	/** Blender's type of modifier. */
+	private String				type;
+	/** JME modifier representation object. */
+	private Object				jmeModifierRepresentation;
+	/** Various additional data used by modifiers.*/
+	private Object				additionalData;
+	/**
+	 * Constructor. Creates modifier object.
+	 * @param type
+	 *        blender's type of modifier
+	 * @param modifier
+	 *        JME modifier representation object
+	 */
+	public Modifier(String type, Object modifier, Object additionalData) {
+		this.type = type;
+		this.jmeModifierRepresentation = modifier;
+		this.additionalData = additionalData;
+	}
+
+	/**
+	 * This method returns JME modifier representation object.
+	 * @return JME modifier representation object
+	 */
+	public Object getJmeModifierRepresentation() {
+		return jmeModifierRepresentation;
+	}
+
+	/**
+	 * This method returns blender's type of modifier.
+	 * @return blender's type of modifier
+	 */
+	public String getType() {
+		return type;
+	}
+	
+	/**
+	 * This method returns additional data stored in the modifier.
+	 * @return the additional data stored in the modifier
+	 */
+	public Object getAdditionalData() {
+		return additionalData;
+	}
+}

+ 102 - 0
engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java

@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.utils;
+
+import java.nio.FloatBuffer;
+import java.util.List;
+
+import com.jme3.util.BufferUtils;
+
+/**
+ * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can
+ * hold the state of the calculations.
+ * @author Marcin Roguski
+ */
+public abstract class AbstractBlenderHelper {
+	/** The version of the blend file. */
+	protected final int blenderVersion;
+
+	/**
+	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
+	 * versions.
+	 * @param blenderVersion
+	 *            the version read from the blend file
+	 */
+	public AbstractBlenderHelper(String blenderVersion) {
+		this.blenderVersion = Integer.parseInt(blenderVersion);
+	}
+	
+	/**
+	 * This method clears the state of the helper so that it can be used for different calculations of another feature.
+	 */
+	public void clearState() { }
+
+	/**
+	 * This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are
+	 * being created and stored in the memory. It can be unwise especially inside loops.
+	 * @param text
+	 *            the text to be checked
+	 * @return <b>true</b> if the text is blank and <b>false</b> otherwise
+	 */
+	protected boolean isBlank(String text) {
+		if (text != null) {
+			for (int i = 0; i < text.length(); ++i) {
+				if (!Character.isWhitespace(text.charAt(i))) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+	
+	/**
+	 * Generate a new FloatBuffer using the given array of float[4] objects. The FloatBuffer will be 4 * data.length
+	 * long and contain the vector data as data[0][0], data[0][1], data[0][2], data[0][3], data[1][0]... etc.
+	 * @param data
+	 *        list of float[4] objects to place into a new FloatBuffer
+	 */
+	protected FloatBuffer createFloatBuffer(List<float[]> data) {
+		if(data == null) {
+			return null;
+		}
+		FloatBuffer buff = BufferUtils.createFloatBuffer(4 * data.size());
+		for(float[] v : data) {
+			if(v != null) {
+				buff.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
+			} else {
+				buff.put(0).put(0).put(0).put(0);
+			}
+		}
+		buff.flip();
+		return buff;
+	}
+}

+ 382 - 0
engine/src/blender/com/jme3/scene/plugins/blender/utils/BlenderInputStream.java

@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.utils;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Logger;
+import java.util.zip.GZIPInputStream;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+
+/**
+ * An input stream with random access to data.
+ * @author Marcin Roguski
+ */
+public class BlenderInputStream extends InputStream {
+	private static final Logger	LOGGER				= Logger.getLogger(BlenderInputStream.class.getName());
+
+	/** The default size of the blender buffer. */
+	private static final int	DEFAULT_BUFFER_SIZE	= 1048576;												//1MB
+	/** The application's asset manager. */
+	private AssetManager		assetManager;
+	/**
+	 * Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes.
+	 */
+	private int					pointerSize;
+	/**
+	 * Type of byte ordering used; 'v' means little endian and 'V' means big endian.
+	 */
+	private char				endianess;
+	/** Version of Blender the file was created in; '248' means version 2.48. */
+	private String				versionNumber;
+	/** The buffer we store the read data to. */
+	protected byte[]			cachedBuffer;
+	/** The total size of the stored data. */
+	protected int				size;
+	/** The current position of the read cursor. */
+	protected int				position;
+
+	/**
+	 * Constructor. The input stream is stored and used to read data.
+	 * @param inputStream
+	 *        the stream we read data from
+	 * @param assetManager
+	 *        the application's asset manager
+	 * @param endianess
+	 *        type of byte ordering used; 'v' means little endian and 'V' means big endian
+	 * @throws BlenderFileException
+	 *         this exception is thrown if the file header has some invalid data
+	 */
+	public BlenderInputStream(InputStream inputStream, AssetManager assetManager) throws BlenderFileException {
+		this.assetManager = assetManager;
+		//the size value will canche while reading the file; the available() method cannot be counted on
+		try {
+			size = inputStream.available();
+		} catch (IOException e) {
+			size = 0;
+		}
+		if(size <= 0) {
+			size = BlenderInputStream.DEFAULT_BUFFER_SIZE;
+		}
+
+		//buffered input stream is used here for much faster file reading
+		BufferedInputStream bufferedInputStream;
+		if(inputStream instanceof BufferedInputStream) {
+			bufferedInputStream = (BufferedInputStream)inputStream;
+		} else {
+			bufferedInputStream = new BufferedInputStream(inputStream);
+		}
+
+		try {
+			this.readStreamToCache(bufferedInputStream);
+		} catch (IOException e) {
+			throw new BlenderFileException("Problems occured while caching the file!", e);
+		}
+
+		try {
+			this.readFileHeader();
+		} catch(BlenderFileException e) {//the file might be packed, don't panic, try one more time ;)
+			this.decompressFile();
+			this.position = 0;
+			this.readFileHeader();
+		}
+	}
+
+	/**
+	 * This method reads the whole stream into a buffer.
+	 * @param inputStream
+	 *        the stream to read the file data from
+	 * @throws IOException 
+	 * 		   an exception is thrown when data read from the stream is invalid or there are problems with i/o
+	 *         operations
+	 */
+	private void readStreamToCache(InputStream inputStream) throws IOException {
+		int data = inputStream.read();
+		cachedBuffer = new byte[size];
+		size = 0;//this will count the actual size
+		while(data != -1) {
+			cachedBuffer[size++] = (byte)data;
+			if(size >= cachedBuffer.length) {//widen the cached array
+				byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)];
+				System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length);
+				cachedBuffer = newBuffer;
+			}
+			data = inputStream.read();
+		}
+	}
+
+	/**
+	 * This method is used when the blender file is gzipped. It decompresses the data and stores it back into the
+	 * cachedBuffer field.
+	 */
+	private void decompressFile() {
+		GZIPInputStream gis = null;
+		try {
+			gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer));
+			this.readStreamToCache(gis);
+		} catch (IOException e) {
+			throw new IllegalStateException("IO errors occured where they should NOT! " +
+											"The data is already buffered at this point!", e);
+		} finally {
+			try {
+				if(gis!=null) {
+					gis.close();
+				}
+			} catch(IOException e) {
+				LOGGER.warning(e.getMessage());
+			}
+		}
+	}
+
+	/**
+	 * This method loads the header from the given stream during instance creation.
+	 * @param inputStream
+	 *        the stream we read the header from
+	 * @throws BlenderFileException
+	 *         this exception is thrown if the file header has some invalid data
+	 */
+	private void readFileHeader() throws BlenderFileException {
+		byte[] identifier = new byte[7];
+		int bytesRead = this.readBytes(identifier);
+		if(bytesRead != 7) {
+			throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!");
+		}
+		String strIdentifier = new String(identifier);
+		if(!"BLENDER".equals(strIdentifier)) {
+			throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!");
+		}
+		char pointerSizeSign = (char)this.readByte();
+		if(pointerSizeSign == '-') {
+			pointerSize = 8;
+		} else if(pointerSizeSign == '_') {
+			pointerSize = 4;
+		} else {
+			throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign);
+		}
+		endianess = (char)this.readByte();
+		if(endianess != 'v' && endianess != 'V') {
+			throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess);
+		}
+		byte[] versionNumber = new byte[3];
+		bytesRead = this.readBytes(versionNumber);
+		if(bytesRead != 3) {
+			throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!");
+		}
+		this.versionNumber = new String(versionNumber);
+	}
+
+	@Override
+	public int read() throws IOException {
+		return this.readByte();
+	}
+	
+	/**
+	 * This method reads 1 byte from the stream.
+	 * It works just in the way the read method does.
+	 * It just not throw an exception because at this moment the whole file
+	 * is loaded into buffer, so no need for IOException to be thrown.
+	 * @return a byte from the stream (1 bytes read)
+	 */
+	public int readByte() {
+		return cachedBuffer[position++] & 0xFF;
+	}
+	
+	/**
+	 * This method reads a bytes number big enough to fill the table. 
+	 * It does not throw exceptions so it is for internal use only.
+	 * @param bytes
+	 *            an array to be filled with data
+	 * @return number of read bytes (a length of array actually)
+	 */
+	private int readBytes(byte[] bytes) {
+		for(int i=0;i<bytes.length;++i) {
+			bytes[i] = (byte) this.readByte();
+		}
+		return bytes.length;
+	}
+	
+	/**
+	 * This method reads 2-byte number from the stream.
+	 * @return a number from the stream (2 bytes read)
+	 */
+	public int readShort() {
+		int part1 = this.readByte();
+		int part2 = this.readByte();
+		if(endianess == 'v') {
+			return (part2 << 8) + part1;
+		} else {
+			return (part1 << 8) + part2;
+		}
+	}
+
+	/**
+	 * This method reads 4-byte number from the stream.
+	 * @return a number from the stream (4 bytes read)
+	 */
+	public int readInt() {
+		int part1 = this.readByte();
+		int part2 = this.readByte();
+		int part3 = this.readByte();
+		int part4 = this.readByte();
+		if(endianess == 'v') {
+			return (part4 << 24) + (part3 << 16) + (part2 << 8) + part1;
+		} else {
+			return (part1 << 24) + (part2 << 16) + (part3 << 8) + part4;
+		}
+	}
+
+	/**
+	 * This method reads 4-byte floating point number (float) from the stream.
+	 * @return a number from the stream (4 bytes read)
+	 */
+	public float readFloat() {
+		int intValue = this.readInt();
+		return Float.intBitsToFloat(intValue);
+	}
+
+	/**
+	 * This method reads 8-byte number from the stream.
+	 * @return a number from the stream (8 bytes read)
+	 */
+	public long readLong() {
+		long part1 = this.readInt();
+		long part2 = this.readInt();
+		long result = -1;
+		if(endianess == 'v') {
+			result = part2 << 32 | part1;
+		} else {
+			result = part1 << 32 | part2;
+		}
+		return result;
+	}
+
+	/**
+	 * This method reads 8-byte floating point number (double) from the stream.
+	 * @return a number from the stream (8 bytes read)
+	 */
+	public double readDouble() {
+		long longValue = this.readLong();
+		return Double.longBitsToDouble(longValue);
+	}
+
+	/**
+	 * This method reads the pointer value. Depending on the pointer size defined in the header, the stream reads either
+	 * 4 or 8 bytes of data.
+	 * @return the pointer value
+	 */
+	public long readPointer() {
+		if(pointerSize == 4) {
+			return this.readInt();
+		}
+		return this.readLong();
+	}
+
+	/**
+	 * This method reads the string. It assumes the string is terminated with zero in the stream.
+	 * @return the string read from the stream
+	 */
+	public String readString() {
+		StringBuilder stringBuilder = new StringBuilder();
+		int data = this.readByte();
+		while(data != 0) {
+			stringBuilder.append((char)data);
+			data = this.readByte();
+		}
+		return stringBuilder.toString();
+	}
+
+	/**
+	 * This method sets the current position of the read cursor.
+	 * @param position
+	 *        the position of the read cursor
+	 */
+	public void setPosition(int position) {
+		this.position = position;
+	}
+
+	/**
+	 * This method returns the position of the read cursor.
+	 * @return the position of the read cursor
+	 */
+	public int getPosition() {
+		return position;
+	}
+
+	/**
+	 * This method returns the blender version number where the file was created.
+	 * @return blender version number
+	 */
+	public String getVersionNumber() {
+		return versionNumber;
+	}
+
+	/**
+	 * This method returns the size of the pointer.
+	 * @return the size of the pointer
+	 */
+	public int getPointerSize() {
+		return pointerSize;
+	}
+
+	/**
+	 * This method returns the application's asset manager.
+	 * @return the application's asset manager
+	 */
+	public AssetManager getAssetManager() {
+		return assetManager;
+	}
+
+	/**
+	 * This method aligns cursor position forward to a given amount of bytes.
+	 * @param bytesAmount
+	 *        the byte amount to which we aligh the cursor
+	 */
+	public void alignPosition(int bytesAmount) {
+		if(bytesAmount <= 0) {
+			throw new IllegalArgumentException("Alignment byte number shoulf be positivbe!");
+		}
+		long move = position % bytesAmount;
+		if(move > 0) {
+			position += bytesAmount - move;
+		}
+	}
+
+	@Override
+	public void close() throws IOException {
+//		cachedBuffer = null;
+//		size = position = 0;
+	}
+}

+ 400 - 0
engine/src/blender/com/jme3/scene/plugins/blender/utils/DataRepository.java

@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.utils;
+
+import java.util.ArrayList;
+import java.util.EmptyStackException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.BlenderKey;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.plugins.blender.data.DnaBlockData;
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.structures.Ipo;
+import com.jme3.scene.plugins.blender.structures.Modifier;
+
+/**
+ * The class that stores temporary data and manages it during loading the belnd file. This class is intended to be used
+ * in a single loading thread. It holds the state of loading operations.
+ * @author Marcin Roguski
+ */
+public class DataRepository {
+	/** The blender key. */
+	private BlenderKey								blenderKey;
+	/** The header of the file block. */
+	private DnaBlockData							dnaBlockData;
+	/** The input stream of the blend file. */
+	private BlenderInputStream						inputStream;
+	/** The asset manager. */
+	private AssetManager							assetManager;
+	/** A map containing the file block headers. The key is the old pointer address. */
+	private Map<Long, FileBlockHeader>				fileBlockHeadersByOma	= new HashMap<Long, FileBlockHeader>();
+	/** A map containing the file block headers. The key is the block code. */
+	private Map<Integer, List<FileBlockHeader>>		fileBlockHeadersByCode	= new HashMap<Integer, List<FileBlockHeader>>();
+	/**
+	 * This map stores the loaded features by their old memory address. The first object in the value table is the
+	 * loaded structure and the second - the structure already converted into proper data.
+	 */
+	private Map<Long, 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[]>();
+	/** A stack that hold the parent structure of currently loaded feature. */
+	private Stack<Structure>						parentStack				= new Stack<Structure>();
+	/** A map storing loaded ipos. The key is the ipo's owner old memory address and the value is the ipo. */
+	private Map<Long, Ipo>							loadedIpos				= new HashMap<Long, Ipo>();
+	/** A list of modifiers for the specified object. */
+	protected Map<Long, List<Modifier>>				modifiers				= new HashMap<Long, List<Modifier>>();
+	/** A map og helpers that perform loading. */
+	private Map<String, AbstractBlenderHelper>		helpers					= new HashMap<String, AbstractBlenderHelper>();
+	
+	/**
+	 * This method sets the blender key.
+	 * @param blenderKey
+	 * 		  the blender key
+	 */
+	public void setBlenderKey(BlenderKey blenderKey) {
+		this.blenderKey = blenderKey;
+	}
+	
+	/**
+	 * This method returns the blender key.
+	 * @return the blender key
+	 */
+	public BlenderKey getBlenderKey() {
+		return blenderKey;
+	}
+	
+	/**
+	 * This method sets the dna block data.
+	 * @param dnaBlockData
+	 *        the dna block data
+	 */
+	public void setBlockData(DnaBlockData dnaBlockData) {
+		this.dnaBlockData = dnaBlockData;
+	}
+
+	/**
+	 * This method returns the dna block data.
+	 * @return the dna block data
+	 */
+	public DnaBlockData getDnaBlockData() {
+		return dnaBlockData;
+	}
+
+	/**
+	 * This method returns the asset manager.
+	 * @return the asset manager
+	 */
+	public AssetManager getAssetManager() {
+		return assetManager;
+	}
+
+	/**
+	 * This method sets the asset manager.
+	 * @param assetManager
+	 *        the asset manager
+	 */
+	public void setAssetManager(AssetManager assetManager) {
+		this.assetManager = assetManager;
+	}
+	
+	/**
+	 * This method returns the input stream of the blend file.
+	 * @return the input stream of the blend file
+	 */
+	public BlenderInputStream getInputStream() {
+		return inputStream;
+	}
+
+	/**
+	 * This method sets the input stream of the blend file.
+	 * @param inputStream
+	 *        the input stream of the blend file
+	 */
+	public void setInputStream(BlenderInputStream inputStream) {
+		this.inputStream = inputStream;
+	}
+
+	/**
+	 * This method adds a file block header to the map. Its old memory address is the key.
+	 * @param oldMemoryAddress
+	 *        the address of the block header
+	 * @param fileBlockHeader
+	 *        the block header to store
+	 */
+	public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) {
+		fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader);
+		List<FileBlockHeader> headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode()));
+		if(headers == null) {
+			headers = new ArrayList<FileBlockHeader>();
+			fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers);
+		}
+		headers.add(fileBlockHeader);
+	}
+
+	/**
+	 * This method returns the block header of a given memory address. If the header is not present then null is
+	 * returned.
+	 * @param oldMemoryAddress
+	 *        the address of the block header
+	 * @return loaded header or null if it was not yet loaded
+	 */
+	public FileBlockHeader getFileBlock(Long oldMemoryAddress) {
+		return fileBlockHeadersByOma.get(oldMemoryAddress);
+	}
+
+	/**
+	 * This method returns a list of file blocks' headers of a specified code.
+	 * @param code
+	 *        the code of file blocks
+	 * @return a list of file blocks' headers of a specified code
+	 */
+	public List<FileBlockHeader> getFileBlocks(Integer code) {
+		return fileBlockHeadersByCode.get(code);
+	}
+
+	/**
+	 * This method clears the saved block headers stored in the features map.
+	 */
+	public void clearFileBlocks() {
+		fileBlockHeadersByOma.clear();
+		fileBlockHeadersByCode.clear();
+	}
+
+	/**
+	 * This method adds a helper instance to the helpers' map.
+	 * @param <T>
+	 *        the type of the helper
+	 * @param clazz
+	 *        helper's class definition
+	 * @param helper
+	 *        the helper instance
+	 */
+	public <T> void putHelper(Class<T> clazz, AbstractBlenderHelper helper) {
+		helpers.put(clazz.getSimpleName(), helper);
+	}
+
+	@SuppressWarnings("unchecked")
+	public <T> T getHelper(Class<?> clazz) {
+		return (T)helpers.get(clazz.getSimpleName());
+	}
+	
+
+	/**
+	 * This method adds a loaded feature to the map. The key is its unique old memory address.
+	 * @param oldMemoryAddress
+	 *        the address of the feature
+	 * @param featureName the name of the feature
+	 * @param structure
+	 *        the filled structure of the feature
+	 * @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) {
+			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);
+		}
+	}
+
+	/**
+	 * This method returns the feature of a given memory address. If the feature is not yet loaded then null is
+	 * returned.
+	 * @param oldMemoryAddress
+	 *        the address of the feature
+	 * @param loadedFeatureDataType
+	 *        the type of data we want to retreive it can be either filled 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);
+		if(result != null) {
+			return result[loadedFeatureDataType.getIndex()];
+		}
+		return null;
+	}
+	
+	/**
+	 * This method returns the feature of a given name. If the feature is not yet loaded then null is
+	 * returned.
+	 * @param featureName
+	 *        the name of the feature
+	 * @param loadedFeatureDataType
+	 *        the type of data we want to retreive it can be either filled structure or already converted feature
+	 * @return loaded feature or null if it was not yet loaded
+	 */
+	public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) {
+		Object[] result = loadedFeaturesByName.get(featureName);
+		if(result != null) {
+			return result[loadedFeatureDataType.getIndex()];
+		}
+		return null;
+	}
+
+	/**
+	 * This method clears the saved features stored in the features map.
+	 */
+	public void clearLoadedFeatures() {
+		loadedFeatures.clear();
+	}
+
+	/**
+	 * This method adds the structure to the parent stack.
+	 * @param parent
+	 *        the structure to be added to the stack
+	 */
+	public void pushParent(Structure parent) {
+		parentStack.push(parent);
+	}
+
+	/**
+	 * This method removes the structure from the top of the parent's stack.
+	 * @return the structure that was removed from the stack
+	 */
+	public Structure popParent() {
+		try {
+			return parentStack.pop();
+		} catch(EmptyStackException e) {
+			return null;
+		}
+	}
+
+	/**
+	 * This method retreives the structure at the top of the parent's stack but does not remove it.
+	 * @return the structure from the top of the stack
+	 */
+	public Structure peekParent() {
+		try {
+			return parentStack.peek();
+		} catch(EmptyStackException e) {
+			return null;
+		}
+	}
+
+	public void addIpo(Long ownerOMA, Ipo ipo) {
+		loadedIpos.put(ownerOMA, ipo);
+	}
+	
+	public Ipo removeIpo(Long ownerOma) {
+		return loadedIpos.remove(ownerOma);
+	}
+	
+	public Ipo getIpo(Long ownerOMA) {
+		return loadedIpos.get(ownerOMA);
+	}
+	
+	/**
+	 * This method adds a new modifier to the list.
+	 * @param ownerOMA
+	 *        the owner's old memory address
+	 * @param modifierType
+	 *        the type of the modifier
+	 * @param loadedModifier
+	 *        the loaded modifier object
+	 */
+	public void addModifier(Long ownerOMA, String modifierType, Object loadedModifier, Object additionalModifierData) {
+		List<Modifier> objectModifiers = this.modifiers.get(ownerOMA);
+		if(objectModifiers == null) {
+			objectModifiers = new ArrayList<Modifier>();
+			this.modifiers.put(ownerOMA, objectModifiers);
+		}
+		objectModifiers.add(new Modifier(modifierType, loadedModifier, additionalModifierData));
+	}
+	
+	/**
+	 * This method returns modifiers for the object specified by its old memory address and the modifier type. If no
+	 * modifiers are found - empty list is returned. If the type is null - all modifiers for the object are returned.
+	 * @param objectOMA
+	 *        object's old memory address
+	 * @param type
+	 *        the type of the modifier
+	 * @return the list of object's modifiers
+	 */
+	public List<Modifier> getModifiers(Long objectOMA, String type) {
+		List<Modifier> result = new ArrayList<Modifier>();
+		List<Modifier> readModifiers = modifiers.get(objectOMA);
+		if(readModifiers != null && readModifiers.size() > 0) {
+			for(Modifier modifier : readModifiers) {
+				if(type==null || type.isEmpty() || modifier.getType().equals(type)) {
+					result.add(modifier);
+				}
+			}
+		}
+		return result;
+	}
+	
+	/**
+	 * This metod returns the default material.
+	 * @return the default material
+	 */
+	public synchronized Material getDefaultMaterial() {
+		if(blenderKey.getDefaultMaterial() == null) {
+			Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
+			defaultMaterial.setColor("Color", ColorRGBA.DarkGray);
+			blenderKey.setDefaultMaterial(defaultMaterial);
+		}
+		return blenderKey.getDefaultMaterial();
+	}
+
+	/**
+	 * This enum defines what loaded data type user wants to retreive. It can be either filled structure or already
+	 * converted data.
+	 * @author Marcin Roguski
+	 */
+	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;
+		}
+	}
+}

+ 156 - 0
engine/src/blender/com/jme3/scene/plugins/blender/utils/DynamicArray.java

@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.utils;
+
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+
+/**
+ * An array that can be dynamically modified/
+ * @author Marcin Roguski
+ * @param <T>
+ *        the type of stored data in the array
+ */
+public class DynamicArray<T> implements Cloneable {
+	/** An array object that holds the required data. */
+	private T[]		array;
+	/**
+	 * This table holds the sizes of dimetions of the dynamic table. It's length specifies the table dimension or a
+	 * pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths:
+	 * dynTable[a][b][c], where a,b,c are stored in the tableSizes table.
+	 */
+	private int[]	tableSizes;
+
+	/**
+	 * Constructor. Builds an empty array of the specified sizes.
+	 * @param tableSizes
+	 *        the sizes of the table
+	 * @throws BlenderFileException
+	 *         an exception is thrown if one of the sizes is not a positive number
+	 */
+	@SuppressWarnings("unchecked")
+	public DynamicArray(int[] tableSizes) throws BlenderFileException {
+		this.tableSizes = tableSizes;
+		int totalSize = 1;
+		for(int size : tableSizes) {
+			if(size <= 0) {
+				throw new BlenderFileException("The size of the table must be positive!");
+			}
+			totalSize *= size;
+		}
+		this.array = (T[])new Object[totalSize];
+	}
+
+	/**
+	 * Constructor. Builds an empty array of the specified sizes.
+	 * @param tableSizes
+	 *        the sizes of the table
+	 * @throws BlenderFileException
+	 *         an exception is thrown if one of the sizes is not a positive number
+	 */
+	public DynamicArray(int[] tableSizes, T[] data) throws BlenderFileException {
+		this.tableSizes = tableSizes;
+		int totalSize = 1;
+		for(int size : tableSizes) {
+			if(size <= 0) {
+				throw new BlenderFileException("The size of the table must be positive!");
+			}
+			totalSize *= size;
+		}
+		if(totalSize != data.length) {
+			throw new IllegalArgumentException("The size of the table does not match the size of the given data!");
+		}
+		this.array = data;
+	}
+
+	@Override
+	public Object clone() throws CloneNotSupportedException {
+		return super.clone();
+	}
+
+	/**
+	 * This method returns a value on the specified position. The dimension of the table is not taken into
+	 * consideration.
+	 * @param position
+	 *        the position of the data
+	 * @return required data
+	 */
+	public T get(int position) {
+		return array[position];
+	}
+
+	/**
+	 * This method returns a value on the specified position in multidimensional array. Be careful not to exceed the
+	 * table boundaries. Check the table's dimension first.
+	 * @param position
+	 *        the position of the data indices of data position
+	 * @return required data required data
+	 */
+	public T get(int... position) {
+		if(position.length != tableSizes.length) {
+			throw new ArrayIndexOutOfBoundsException("The table accepts " + tableSizes.length + " indexing number(s)!");
+		}
+		int index = 0;
+		for(int i = 0; i < position.length - 1; ++i) {
+			index += position[i] * tableSizes[i + 1];
+		}
+		index += position[position.length - 1];
+		return array[index];
+	}
+
+	/**
+	 * This method returns the total amount of data stored in the array.
+	 * @return the total amount of data stored in the array
+	 */
+	public int getTotalSize() {
+		return array.length;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder result = new StringBuilder();
+		if(array instanceof Character[]) {//in case of character array we convert it to String
+			for(int i = 0; i < array.length && (Character)array[i] != '\0'; ++i) {//strings are terminater with '0'
+				result.append(array[i]);
+			}
+		} else {
+			result.append('[');
+			for(int i = 0; i < array.length; ++i) {
+				result.append(array[i].toString());
+				if(i + 1 < array.length) {
+					result.append(',');
+				}
+			}
+			result.append(']');
+		}
+		return result.toString();
+	}
+}

+ 109 - 0
engine/src/blender/com/jme3/scene/plugins/blender/utils/IBlenderConverter.java

@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.utils;
+
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+
+/**
+ * This interface provides an abstraction to converting loaded blender structures into data structures. The data
+ * structures can vary and therefore one can use the loader for different kind of engines.
+ * @author Marcin Roguski
+ * @param <NodeType>
+ *        the type of the scene node element
+ * @param <CameraType>
+ *        the type of camera element
+ * @param <LightType>
+ *        the type of light element
+ * @param <ObjectType>
+ *        the type of object element
+ * @param <MeshType>
+ *        the type of mesh element
+ * @param <MaterialType>
+ *        the type of material element
+ */
+//TODO: ujednolicić wyrzucane wyjątki
+public interface IBlenderConverter<NodeType, CameraType, LightType, ObjectType, MeshType, MaterialType> {
+	/**
+	 * This method reads converts the given structure into scene. The given structure needs to be filled with the
+	 * appropriate data.
+	 * @param structure
+	 *        the structure we read the scene from
+	 * @return the scene feature
+	 */
+	NodeType toScene(Structure structure);
+
+	/**
+	 * This method reads converts the given structure into camera. The given structure needs to be filled with the
+	 * appropriate data.
+	 * @param structure
+	 *        the structure we read the camera from
+	 * @return the camera feature
+	 */
+	CameraType toCamera(Structure structure) throws BlenderFileException;
+
+	/**
+	 * This method reads converts the given structure into light. The given structure needs to be filled with the
+	 * appropriate data.
+	 * @param structure
+	 *        the structure we read the light from
+	 * @return the light feature
+	 */
+	LightType toLight(Structure structure) throws BlenderFileException;
+
+	/**
+	 * This method reads converts the given structure into objct. The given structure needs to be filled with the
+	 * appropriate data.
+	 * @param structure
+	 *        the structure we read the object from
+	 * @return the object feature
+	 */
+	ObjectType toObject(Structure structure) throws BlenderFileException;
+
+	/**
+	 * This method reads converts the given structure into mesh. The given structure needs to be filled with the
+	 * appropriate data.
+	 * @param structure
+	 *        the structure we read the mesh from
+	 * @return the mesh feature
+	 */
+	MeshType toMesh(Structure structure) throws BlenderFileException;
+
+	/**
+	 * This method reads converts the given structure into material. The given structure needs to be filled with the
+	 * appropriate data.
+	 * @param structure
+	 *        the structure we read the material from
+	 * @return the material feature
+	 */
+	MaterialType toMaterial(Structure structure) throws BlenderFileException;
+}

+ 161 - 0
engine/src/blender/com/jme3/scene/plugins/blender/utils/JmeConverter.java

@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.utils;
+
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.jme3.asset.BlenderKey.FeaturesToLoad;
+import com.jme3.asset.BlenderKey.WorldData;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.Light;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+import com.jme3.scene.plugins.blender.helpers.CameraHelper;
+import com.jme3.scene.plugins.blender.helpers.LightHelper;
+import com.jme3.scene.plugins.blender.helpers.MaterialHelper;
+import com.jme3.scene.plugins.blender.helpers.MeshHelper;
+import com.jme3.scene.plugins.blender.helpers.ObjectHelper;
+
+/**
+ * This class converts blender file blocks into jMonkeyEngine data structures.
+ * @author Marcin Roguski
+ */
+public class JmeConverter implements IBlenderConverter<Node, Camera, Light, Object, List<Geometry>, Material> {
+	private static final Logger		LOGGER						= Logger.getLogger(JmeConverter.class.getName());
+
+	private final DataRepository	dataRepository;
+
+	/**
+	 * Constructor. Creates the loader and checks if the given data is correct.
+	 * @param dataRepository
+	 *        the data repository; it should have the following field set: - asset manager - blender key - dna block
+	 *        data - blender input stream Otherwise IllegalArgumentException will be thrown.
+	 * @param featuresToLoad
+	 *        bitwise flag describing what features are to be loaded
+	 * @see FeaturesToLoad FeaturesToLoad
+	 */
+	public JmeConverter(DataRepository dataRepository) {
+		//validating the given data first
+		if(dataRepository.getAssetManager() == null) {
+			throw new IllegalArgumentException("Cannot find asset manager!");
+		}
+		if(dataRepository.getBlenderKey() == null) {
+			throw new IllegalArgumentException("Cannot find blender key!");
+		}
+		if(dataRepository.getDnaBlockData() == null) {
+			throw new IllegalArgumentException("Cannot find dna block!");
+		}
+		if(dataRepository.getInputStream() == null) {
+			throw new IllegalArgumentException("Cannot find blender file stream!");
+		}
+		this.dataRepository = dataRepository;
+	}
+
+	@Override
+	public Node toScene(Structure structure) {//TODO: poprawny import sceny
+		if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) {
+			return null;
+		}
+		Structure id = (Structure)structure.getFieldValue("id");
+		String sceneName = id.getFieldValue("name").toString();
+		return new Node(sceneName);
+	}
+
+	@Override
+	public Camera toCamera(Structure structure) throws BlenderFileException {
+		if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) == 0) {
+			return null;
+		}
+		CameraHelper cameraHelper = dataRepository.getHelper(CameraHelper.class);
+		return cameraHelper.toCamera(structure);
+	}
+
+	@Override
+	public Light toLight(Structure structure) throws BlenderFileException {
+		if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) == 0) {
+			return null;
+		}
+		LightHelper lightHelper = dataRepository.getHelper(LightHelper.class);
+		return lightHelper.toLight(structure, dataRepository);
+	}
+
+	@Override
+	public Object toObject(Structure structure) throws BlenderFileException {
+		if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) == 0) {
+			return null;
+		}
+		ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class);
+		return objectHelper.toObject(structure, dataRepository);
+	}
+
+	@Override
+	public List<Geometry> toMesh(Structure structure) throws BlenderFileException {
+		MeshHelper meshHelper = dataRepository.getHelper(MeshHelper.class);
+		return meshHelper.toMesh(structure, dataRepository);
+	}
+
+	@Override
+	public Material toMaterial(Structure structure) throws BlenderFileException {
+		if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) == 0) {
+			return null;
+		}
+		MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class);
+		return materialHelper.toMaterial(structure, dataRepository);
+	}
+
+	/**
+	 * This method returns the data read from the WORLD file block. The block contains data that can be stored as
+	 * separate jme features and therefore cannot be returned as a single jME scene feature.
+	 * @param structure
+	 *        the structure with WORLD block data
+	 * @return data read from the WORLD block that can be added to the scene
+	 */
+	public WorldData toWorldData(Structure structure) {
+		WorldData result = new WorldData();
+
+		//reading ambient light
+		AmbientLight ambientLight = new AmbientLight();
+		float ambr = ((Number)structure.getFieldValue("ambr")).floatValue();
+		float ambg = ((Number)structure.getFieldValue("ambg")).floatValue();
+		float ambb = ((Number)structure.getFieldValue("ambb")).floatValue();
+		ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f));
+		result.setAmbientLight(ambientLight);
+
+		return result;
+	}
+}

+ 175 - 0
engine/src/blender/com/jme3/scene/plugins/blender/utils/Pointer.java

@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.jme3.scene.plugins.blender.data.FileBlockHeader;
+import com.jme3.scene.plugins.blender.data.Structure;
+import com.jme3.scene.plugins.blender.exception.BlenderFileException;
+
+/**
+ * A class that represents a pointer of any level that can be stored in the file.
+ * @author Marcin Roguski
+ */
+public class Pointer {
+	/** The data repository. */
+	private DataRepository	dataRepository;
+	/** The level of the pointer. */
+	private int				pointerLevel;
+	/** The address in file it points to. */
+	private long			oldMemoryAddress;
+	/** This variable indicates if the field is a function pointer. */
+	public boolean			function;
+
+	/**
+	 * Constructr. Stores the basic data about the pointer.
+	 * @param pointerLevel
+	 *        the level of the pointer
+	 * @param function
+	 *        this variable indicates if the field is a function pointer
+	 * @param dataRepository
+	 *        the repository f data; used in fetching the value that the pointer points
+	 */
+	public Pointer(int pointerLevel, boolean function, DataRepository dataRepository) {
+		this.pointerLevel = pointerLevel;
+		this.function = function;
+		this.dataRepository = dataRepository;
+	}
+
+	/**
+	 * This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method
+	 * for this.
+	 * @param inputStream
+	 *        the stream we read the pointer value from
+	 */
+	public void fill(BlenderInputStream inputStream) {
+		oldMemoryAddress = inputStream.readPointer();
+	}
+
+	/**
+	 * This method fetches the data stored under the given address.
+	 * @param inputStream
+	 *        the stream we read data from
+	 * @param dataIndices
+	 *        the offset of the data in the table pointed by the pointer
+	 * @return the data read from the file
+	 * @throws BlenderFileException
+	 *         this exception is thrown when the blend file structure is somehow invalid or corrupted
+	 */
+	public List<Structure> fetchData(BlenderInputStream inputStream) throws BlenderFileException {
+		if(oldMemoryAddress == 0) {
+			throw new NullPointerException("The pointer points to nothing!");
+		}
+		List<Structure> structures = null;
+		FileBlockHeader dataFileBlock = dataRepository.getFileBlock(oldMemoryAddress);
+		if(pointerLevel > 1) {
+			int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount();
+			for(int i = 0; i < pointersAmount; ++i) {
+				inputStream.setPosition(dataFileBlock.getBlockPosition() + inputStream.getPointerSize() * i);
+				long oldMemoryAddress = inputStream.readPointer();
+				if(oldMemoryAddress != 0L) {
+					Pointer p = new Pointer(pointerLevel - 1, this.function, dataRepository);
+					p.oldMemoryAddress = oldMemoryAddress;
+					if(structures == null) {
+						structures = p.fetchData(inputStream);
+					} else {
+						structures.addAll(p.fetchData(inputStream));
+					}
+				}
+			}
+		} else {
+			inputStream.setPosition(dataFileBlock.getBlockPosition());
+			structures = new ArrayList<Structure>(dataFileBlock.getCount());
+			for(int i = 0; i < dataFileBlock.getCount(); ++i) {
+				Structure structure = dataRepository.getDnaBlockData().getStructure(dataFileBlock.getSdnaIndex());
+				structure.fill(inputStream);
+				structures.add(structure);
+			}
+			return structures;
+		}
+		return structures;
+	}
+
+	/**
+	 * This method indicates if this pointer points to a function.
+	 * @return <b>true</b> if this is a function pointer and <b>false</b> otherwise
+	 */
+	public boolean isFunction() {
+		return function;
+	}
+
+	/**
+	 * This method indicates if this is a null-pointer or not.
+	 * @return <b>true</b> if the pointer is null and <b>false</b> otherwise
+	 */
+	public boolean isNull() {
+		return oldMemoryAddress == 0;
+	}
+
+	/**
+	 * This method returns the old memory address of the structure pointed by the pointer.
+	 * @return the old memory address of the structure pointed by the pointer
+	 */
+	public long getOldMemoryAddress() {
+		return oldMemoryAddress;
+	}
+
+	@Override
+	public String toString() {
+		return oldMemoryAddress == 0 ? "{$null$}" : "{$" + oldMemoryAddress + "$}";
+	}
+
+	@Override
+	public int hashCode() {
+		return 31 + (int)(oldMemoryAddress ^ oldMemoryAddress >>> 32);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj == null) {
+			return false;
+		}
+		if(this.getClass() != obj.getClass()) {
+			return false;
+		}
+		Pointer other = (Pointer)obj;
+		if(oldMemoryAddress != other.oldMemoryAddress) {
+			return false;
+		}
+		return true;
+	}
+}

+ 1 - 0
engine/src/desktop/com/jme3/asset/Desktop.cfg

@@ -17,4 +17,5 @@ LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml
 LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml
 LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material
 LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene
+LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend
 LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib