diff --git a/engine/nbproject/build-impl.xml b/engine/nbproject/build-impl.xml
index 533455b8b..b3a1529f7 100644
--- a/engine/nbproject/build-impl.xml
+++ b/engine/nbproject/build-impl.xml
@@ -180,6 +180,7 @@ is divided into following sections:
+
@@ -263,6 +264,7 @@ is divided into following sections:
Must set src.lwjgl-oal.dir
Must set src.lwjgl-ogl.dir
Must set src.ogre.dir
+ Must set src.blender.dir
Must set src.pack.dir
Must set src.jheora.dir
Must set src.test.dir
@@ -289,7 +291,7 @@ is divided into following sections:
-
+
@@ -329,7 +331,7 @@ is divided into following sections:
-
+
@@ -361,7 +363,7 @@ is divided into following sections:
-
+
@@ -659,7 +661,7 @@ is divided into following sections:
-
+
@@ -678,6 +680,7 @@ is divided into following sections:
+
@@ -703,7 +706,7 @@ is divided into following sections:
Must select some files in the IDE or set javac.includes
-
+
@@ -965,6 +968,9 @@ is divided into following sections:
+
+
+
@@ -1027,6 +1033,9 @@ is divided into following sections:
+
+
+
diff --git a/engine/nbproject/genfiles.properties b/engine/nbproject/genfiles.properties
index 101bdc872..f24a52947 100644
--- a/engine/nbproject/genfiles.properties
+++ b/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/build-impl.xml.stylesheet.CRC32=0c01fd8e@1.43.1.45
nbproject/profiler-build-impl.xml.data.CRC32=aff514c1
nbproject/profiler-build-impl.xml.script.CRC32=abda56ed
diff --git a/engine/nbproject/project.properties b/engine/nbproject/project.properties
index 876511762..96069ac85 100644
--- a/engine/nbproject/project.properties
+++ b/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
diff --git a/engine/nbproject/project.xml b/engine/nbproject/project.xml
index 8aea04430..d501bc9eb 100644
--- a/engine/nbproject/project.xml
+++ b/engine/nbproject/project.xml
@@ -25,6 +25,7 @@
+
diff --git a/engine/src/blender/com/jme3/asset/BlenderKey.java b/engine/src/blender/com/jme3/asset/BlenderKey.java
new file mode 100644
index 000000000..cdb5e23ae
--- /dev/null
+++ b/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> 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>();
+ animations.put(objectName, new HashMap());
+ }
+ Map objectAnimations = animations.get(objectName);
+ if(objectAnimations == null) {
+ objectAnimations = new HashMap();
+ 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 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 getAnimationNames(String objectName) {
+ Map 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> 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> 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 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>(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 objectAnimations = new HashMap(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 scenes;
+ /** Objects from all scenes. */
+ private List objects;
+ /** Materials from all objects. */
+ private List materials;
+ /** Textures from all objects. */
+ private List textures;
+ /** Animations of all objects. */
+ private List animations;
+ /** All cameras from the file. */
+ private List cameras;
+ /** All lights from the file. */
+ private List 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();
+ }
+ if((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) {
+ objects = new ArrayList();
+ if((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) {
+ materials = new ArrayList();
+ if((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) {
+ textures = new ArrayList();
+ }
+ }
+ if((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) {
+ animations = new ArrayList();
+ }
+ }
+ if((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
+ cameras = new ArrayList();
+ }
+ if((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) {
+ lights = new ArrayList();
+ }
+ }
+
+ /**
+ * 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 getScenes() {
+ return scenes;
+ }
+
+ /**
+ * This method returns all loaded objects.
+ * @return all loaded objects
+ */
+ public List getObjects() {
+ return objects;
+ }
+
+ /**
+ * This method returns all loaded materials.
+ * @return all loaded materials
+ */
+ public List getMaterials() {
+ return materials;
+ }
+
+ /**
+ * This method returns all loaded textures.
+ * @return all loaded textures
+ */
+ public List getTextures() {
+ return textures;
+ }
+
+ /**
+ * This method returns all loaded animations.
+ * @return all loaded animations
+ */
+ public List getAnimations() {
+ return animations;
+ }
+
+ /**
+ * This method returns all loaded cameras.
+ * @return all loaded cameras
+ */
+ public List getCameras() {
+ return cameras;
+ }
+
+ /**
+ * This method returns all loaded lights.
+ * @return all loaded lights
+ */
+ public List 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 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;
+ }
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
new file mode 100644
index 000000000..bc8a51b5d
--- /dev/null
+++ b/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 blocks = new ArrayList();
+ 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;
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java
new file mode 100644
index 000000000..096ec23e1
--- /dev/null
+++ b/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 blocks = new ArrayList();
+ 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 objects = loadingResults.getObjects();
+ if(objects.size() > 0) {
+ Node modelNode = new Node(blenderKey.getName());
+ for(Iterator 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;
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/data/DnaBlockData.java b/engine/src/blender/com/jme3/scene/plugins/blender/data/DnaBlockData.java
new file mode 100644
index 000000000..5872039ac
--- /dev/null
+++ b/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 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(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();
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/data/Field.java b/engine/src/blender/com/jme3/scene/plugins/blender/data/Field.java
new file mode 100644
index 000000000..e57862ece
--- /dev/null
+++ b/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(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(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(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(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(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(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(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(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 lengths = new ArrayList(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();
+ }
+}
\ No newline at end of file
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/data/FileBlockHeader.java b/engine/src/blender/com/jme3/scene/plugins/blender/data/FileBlockHeader.java
new file mode 100644
index 000000000..b69f92661
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/data/Structure.java b/engine/src/blender/com/jme3/scene/plugins/blender/data/Structure.java
new file mode 100644
index 000000000..340a0183d
--- /dev/null
+++ b/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 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 result = new LinkedList();
+ 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 PRIMARY_TYPES = new HashMap(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);
+ }
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/exception/BlenderFileException.java b/engine/src/blender/com/jme3/scene/plugins/blender/exception/BlenderFileException.java
new file mode 100644
index 000000000..7b1b52267
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ArmatureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ArmatureHelper.java
new file mode 100644
index 000000000..3312784de
--- /dev/null
+++ b/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 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 tracks = new ArrayList();
+ for(Structure actionGroup : actionGroups) {
+ String name = actionGroup.getFieldValue("name").toString();
+ Integer boneIndex = bonesMap.get(name);
+ if(boneIndex != null) {
+ List 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 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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/CameraHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/CameraHelper.java
new file mode 100644
index 000000000..1576419c0
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ConstraintHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ConstraintHelper.java
new file mode 100644
index 000000000..48473101c
--- /dev/null
+++ b/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
+ }
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/CurvesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/CurvesHelper.java
new file mode 100644
index 000000000..1f3f38d6f
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/IpoHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/IpoHelper.java
new file mode 100644
index 000000000..65d5f6173
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/LightHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/LightHelper.java
new file mode 100644
index 000000000..fe11d1ccf
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/MaterialHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/MaterialHelper.java
new file mode 100644
index 000000000..a10590c5a
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/MeshHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/MeshHelper.java
new file mode 100644
index 000000000..904af02f4
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ModifierHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ModifierHelper.java
new file mode 100644
index 000000000..4c39b9347
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/NoiseHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/NoiseHelper.java
new file mode 100644
index 000000000..640349928
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ObjectHelper.java
new file mode 100644
index 000000000..b469a9ed4
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ParticlesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/ParticlesHelper.java
new file mode 100644
index 000000000..c0c16121b
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/TextureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/TextureHelper.java
new file mode 100644
index 000000000..7f9c3992d
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ArmatureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ArmatureHelper.java
new file mode 100644
index 000000000..1118e6ba5
--- /dev/null
+++ b/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 bonesMap = new HashMap();
+ /** A map of bones and their old memory addresses. */
+ protected Map bonesOMAs = new HashMap();
+ /** This list contains bones hierarchy and their matrices. It is later converted into jme bones. */
+ protected List boneDataRoots = new ArrayList();
+
+ /**
+ * 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 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 getGroupToBoneIndexMap(Structure defBaseStructure, DataRepository dataRepository) throws BlenderFileException {
+ Map result = null;
+ if(bonesMap != null && bonesMap.size() != 0) {
+ result = new HashMap();
+ List 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 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 tracks = new ArrayList();
+ 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 boneMat = (DynamicArray)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 sizeArray = (DynamicArray) 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 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 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 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();
+ 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 bones = new ArrayList(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();
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CameraHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CameraHelper.java
new file mode 100644
index 000000000..04b00d1f2
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ConstraintHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ConstraintHelper.java
new file mode 100644
index 000000000..2792b0fe4
--- /dev/null
+++ b/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 constraints = new HashMap();
+
+ /**
+ * 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= 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 bonesList = new ArrayList();
+ 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> constraintsIpos = new HashMap>();
+ Pointer pActions = (Pointer)objectStructure.getFieldValue("action");
+ if(!pActions.isNull()) {
+ List actions = pActions.fetchData(dataRepository.getInputStream());
+ for(Structure action : actions) {
+ Structure chanbase = (Structure)action.getFieldValue("chanbase");
+ List actionChannels = chanbase.evaluateListBase(dataRepository);
+ for(Structure actionChannel : actionChannels) {
+ Map ipos = new HashMap();
+ Structure constChannels = (Structure)actionChannel.getFieldValue("constraintChannels");
+ List 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 constraintsList = new ArrayList();
+ Pointer pPose = (Pointer)objectStructure.getFieldValue("pose");//TODO: what if the object has two armatures ????
+ if(!pPose.isNull()) {
+ //getting pose channels
+ List 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 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 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 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();
+ }
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java
new file mode 100644
index 000000000..f5ca3c982
--- /dev/null
+++ b/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 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 nurbStructures = ((Structure)curveStructure.getFieldValue("nurb")).evaluateListBase(dataRepository);
+ Map> nurbs = new HashMap>();
+ for(Structure nurb : nurbStructures) {
+ Number matNumber = (Number) nurb.getFieldValue("mat_nr");
+ List nurbList = nurbs.get(matNumber);
+ if(nurbList==null) {
+ nurbList = new ArrayList();
+ 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 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 conrtolPoints = new ArrayList(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(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(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 result = new ArrayList(nurbs.size());
+ for(Entry> nurbEntry : nurbs.entrySet()) {
+ for(Structure nurb : nurbEntry.getValue()) {
+ int type = ((Number)nurb.getFieldValue("type")).intValue();
+ List 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 loadBezierCurve(Vector3f loc, Structure nurb, List bevelObject, Curve taperObject,
+ DataRepository dataRepository) throws BlenderFileException {
+ Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt");
+ List result = new ArrayList();
+ 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 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 loadNurb(Vector3f loc, Structure nurb, List bevelObject, Curve taperObject,
+ DataRepository dataRepository) throws BlenderFileException {
+ //loading the knots
+ List[] knots = new List[2];
+ Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") };
+ for(int i=0;i(knotsAmount);
+ for(int j=0;j bPoints = ((Pointer)nurb.getFieldValue("bp")).fetchData(dataRepository.getInputStream());
+ List> controlPoints = new ArrayList>(pntsV);
+ for(int i=0;i uControlPoints = new ArrayList(pntsU);
+ for(int j=0;j vec = (DynamicArray)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 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(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(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 applyBevelAndTaper(Curve curve, List 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 normalMap = new HashMap();//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=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 result = new ArrayList(vertexBuffers.length);
+ for(int i=0;i 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 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 locArray = (DynamicArray)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());
+ }
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/IpoHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/IpoHelper.java
new file mode 100644
index 000000000..1a6eacdc7
--- /dev/null
+++ b/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 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 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!");
+ }
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/LightHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/LightHelper.java
new file mode 100644
index 000000000..ce9da9f47
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MaterialHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MaterialHelper.java
new file mode 100644
index 000000000..f273053cb
--- /dev/null
+++ b/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 mtexs = (DynamicArray) structure.getFieldValue("mtex");
+ for (int i = 0; i < mtexs.getTotalSize(); ++i) {
+ Pointer p = mtexs.get(i);
+ if (!p.isNull()) {
+ List 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 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 textures = new HashMap(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 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 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 true if the texture exists in the material and false 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 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;
+ }
+ }
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java
new file mode 100644
index 000000000..13f427fe1
--- /dev/null
+++ b/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 toMesh(Structure structure, DataRepository dataRepository) throws BlenderFileException {
+ List geometries = (List) dataRepository.getLoadedFeature(structure.getOldMemoryAddress(),
+ LoadedFeatureDataType.LOADED_FEATURE);
+ if (geometries != null) {
+ List copiedGeometries = new ArrayList(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 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> meshesMap = new HashMap>();
+ Pointer pMFace = (Pointer) structure.getFieldValue("mface");
+ List mFaces = pMFace.fetchData(dataRepository.getInputStream());
+
+ Pointer pMTFace = (Pointer) structure.getFieldValue("mtface");
+ List uvCoordinates = null;
+ List 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();// TODO: calculate the amount of coordinates if possible
+ }
+
+ // normalMap merges normals of faces that will be rendered smooth
+ Map normalMap = new HashMap(verticesAmount);
+
+ List normalList = new ArrayList();
+ List vertexList = new ArrayList();
+ Map materialNumberToTexture = new HashMap();// 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> vertexReferenceMap = new HashMap>(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 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) 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 indexList = meshesMap.get(materialNumber);
+ if (indexList == null) {
+ indexList = new ArrayList();
+ 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 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 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(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> meshEntry : meshesMap.entrySet()) {
+ Mesh mesh = new Mesh();
+
+ // creating vertices indices for this mesh
+ List 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 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> vertexReferenceMap) {
+ List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
+ if (referenceList == null) {
+ referenceList = new ArrayList();
+ 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 getVerticesColors(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException {
+ Pointer pMCol = (Pointer) meshStructure.getFieldValue("mcol");
+ List verticesColors = null;
+ List mCol = null;
+ if (!pMCol.isNull()) {
+ verticesColors = new LinkedList();
+ 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 mVerts = pMVert.fetchData(dataRepository.getInputStream());
+ for (int i = 0; i < verticesAmount; ++i) {
+ DynamicArray coordinates = (DynamicArray) 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> vertexReferenceMap, Map 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 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 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 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 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;
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ModifierHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ModifierHelper.java
new file mode 100644
index 000000000..bd266eb32
--- /dev/null
+++ b/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 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 params = new HashMap();
+
+ 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 offsetArray = (DynamicArray)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 scaleArray = (DynamicArray)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 animationNames = dataRepository.getBlenderKey().getAnimationNames(objectName);
+ if(animationNames != null && animationNames.size() > 0) {
+ ArrayList animations = new ArrayList();
+ List 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 meshes = new ArrayList();
+ 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 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 anims = new HashMap();
+ 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 meshesList = new ArrayList();
+ List 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 animations = new HashMap();
+ for(String animationName : control.getAnimationNames()) {
+ animations.put(animationName, control.getAnim(animationName));
+ }
+ for(Entry 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 modifierData = (Map) 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;i0.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 bones = new ArrayList(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()]));
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/NoiseHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/NoiseHelper.java
new file mode 100644
index 000000000..75c6a0428
--- /dev/null
+++ b/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 noiseFunctions = new HashMap();
+ 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 distanceFunctions = new HashMap();
+ 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 musgraveFunctions = new HashMap();
+ 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 true if calculation suceedess and false 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);
+ }
+}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java
new file mode 100644
index 000000000..19085d311
--- /dev/null
+++ b/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 meshesArray = pMesh.fetchData(dataRepository.getInputStream());
+ List geometries = meshHelper.toMesh(meshesArray.get(0), dataRepository);
+ for(Geometry geometry : geometries) {
+ node.attachChild(geometry);
+ }
+ node.setLocalTransform(t);
+
+ //applying all modifiers
+ List 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 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 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 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 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 loc = (DynamicArray)objectStructure.getFieldValue("loc");
+ DynamicArray