From 36d041fae69b1af525adf44e6ea4cfff4e733a5e Mon Sep 17 00:00:00 2001 From: "nor..67" Date: Tue, 7 Jun 2011 11:43:34 +0000 Subject: [PATCH] - add blender loader git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7554 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- engine/nbproject/build-impl.xml | 19 +- engine/nbproject/genfiles.properties | 4 +- engine/nbproject/project.properties | 227 +- engine/nbproject/project.xml | 1 + .../blender/com/jme3/asset/BlenderKey.java | 756 +++++++ .../scene/plugins/blender/BlenderLoader.java | 193 ++ .../plugins/blender/BlenderModelLoader.java | 157 ++ .../plugins/blender/data/DnaBlockData.java | 210 ++ .../scene/plugins/blender/data/Field.java | 319 +++ .../plugins/blender/data/FileBlockHeader.java | 201 ++ .../scene/plugins/blender/data/Structure.java | 311 +++ .../exception/BlenderFileException.java | 74 + .../blender/helpers/ArmatureHelper.java | 132 ++ .../plugins/blender/helpers/CameraHelper.java | 51 + .../blender/helpers/ConstraintHelper.java | 37 + .../plugins/blender/helpers/CurvesHelper.java | 17 + .../plugins/blender/helpers/IpoHelper.java | 18 + .../plugins/blender/helpers/LightHelper.java | 48 + .../blender/helpers/MaterialHelper.java | 44 + .../plugins/blender/helpers/MeshHelper.java | 48 + .../blender/helpers/ModifierHelper.java | 48 + .../plugins/blender/helpers/NoiseHelper.java | 51 + .../plugins/blender/helpers/ObjectHelper.java | 48 + .../blender/helpers/ParticlesHelper.java | 17 + .../blender/helpers/TextureHelper.java | 82 + .../blender/helpers/v249/ArmatureHelper.java | 370 ++++ .../blender/helpers/v249/CameraHelper.java | 57 + .../helpers/v249/ConstraintHelper.java | 734 +++++++ .../blender/helpers/v249/CurvesHelper.java | 590 ++++++ .../blender/helpers/v249/IpoHelper.java | 114 ++ .../blender/helpers/v249/LightHelper.java | 99 + .../blender/helpers/v249/MaterialHelper.java | 589 ++++++ .../blender/helpers/v249/MeshHelper.java | 599 ++++++ .../blender/helpers/v249/ModifierHelper.java | 527 +++++ .../blender/helpers/v249/NoiseHelper.java | 1531 ++++++++++++++ .../blender/helpers/v249/ObjectHelper.java | 407 ++++ .../blender/helpers/v249/ParticlesHelper.java | 118 ++ .../blender/helpers/v249/TextureHelper.java | 1818 +++++++++++++++++ .../blender/helpers/v249/noiseconstants.dat | Bin 0 -> 19598 bytes .../structures/AbstractInfluenceFunction.java | 225 ++ .../blender/structures/BezierCurve.java | 137 ++ .../blender/structures/Constraint.java | 162 ++ .../blender/structures/ConstraintType.java | 143 ++ .../scene/plugins/blender/structures/Ipo.java | 176 ++ .../plugins/blender/structures/Modifier.java | 56 + .../blender/utils/AbstractBlenderHelper.java | 102 + .../blender/utils/BlenderInputStream.java | 382 ++++ .../plugins/blender/utils/DataRepository.java | 400 ++++ .../plugins/blender/utils/DynamicArray.java | 156 ++ .../blender/utils/IBlenderConverter.java | 109 + .../plugins/blender/utils/JmeConverter.java | 161 ++ .../scene/plugins/blender/utils/Pointer.java | 175 ++ engine/src/desktop/com/jme3/asset/Desktop.cfg | 1 + 53 files changed, 12931 insertions(+), 120 deletions(-) create mode 100644 engine/src/blender/com/jme3/asset/BlenderKey.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/data/DnaBlockData.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/data/Field.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/data/FileBlockHeader.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/data/Structure.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/exception/BlenderFileException.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/ArmatureHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/CameraHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/ConstraintHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/CurvesHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/IpoHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/LightHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/MaterialHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/MeshHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/ModifierHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/NoiseHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/ObjectHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/ParticlesHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/TextureHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ArmatureHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CameraHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ConstraintHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/CurvesHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/IpoHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/LightHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MaterialHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/MeshHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ModifierHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/NoiseHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ObjectHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ParticlesHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/TextureHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/noiseconstants.dat create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/structures/AbstractInfluenceFunction.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/structures/BezierCurve.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/structures/Constraint.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/structures/ConstraintType.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/structures/Ipo.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/structures/Modifier.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/utils/BlenderInputStream.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/utils/DataRepository.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/utils/DynamicArray.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/utils/IBlenderConverter.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/utils/JmeConverter.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/utils/Pointer.java 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 size = (DynamicArray)objectStructure.getFieldValue("size"); + DynamicArray rot = (DynamicArray)objectStructure.getFieldValue("rot"); + + Pointer parent = (Pointer) objectStructure.getFieldValue("parent"); + Matrix4f parentInv = parent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv"); + + Matrix4f globalMatrix = new Matrix4f(); + globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); + globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue())); + Matrix4f localMatrix = parentInv.mult(globalMatrix); + + Vector3f translation = localMatrix.toTranslationVector(); + Quaternion rotation = localMatrix.toRotationQuat(); + //getting the scale + float scaleX = (float) Math.sqrt(parentInv.m00 * parentInv.m00 + parentInv.m10 * parentInv.m10 + parentInv.m20 * parentInv.m20); + float scaleY = (float) Math.sqrt(parentInv.m01 * parentInv.m01 + parentInv.m11 * parentInv.m11 + parentInv.m21 * parentInv.m21); + float scaleZ = (float) Math.sqrt(parentInv.m02 * parentInv.m02 + parentInv.m12 * parentInv.m12 + parentInv.m22 * parentInv.m22); + Vector3f scale = new Vector3f(size.get(0).floatValue() * scaleX, + size.get(1).floatValue() * scaleY, + size.get(2).floatValue() * scaleZ); + if(fixUpAxis) { + float y = translation.y; + translation.y = translation.z; + translation.z = y; + rotation.multLocal(this.upAxisRotationQuaternion); + } + Transform t = new Transform(translation, rotation); + t.setScale(scale); + return t; + } + + /** + * This method returns the transformation matrix of the given object structure. + * @param objectStructure + * the structure with object's data + * @return object's transformation matrix + */ + public Matrix4f getTransformationMatrix(Structure objectStructure) { + return this.getMatrix(objectStructure, "obmat"); + } + + /** + * This method returns the matrix of a given name for the given object structure. + * @param objectStructure + * the structure with object's data + * @param matrixName + * the name of the matrix structure + * @return object's matrix + */ + @SuppressWarnings("unchecked") + protected Matrix4f getMatrix(Structure objectStructure, String matrixName) { + Matrix4f result = new Matrix4f(); + DynamicArray obmat = (DynamicArray)objectStructure.getFieldValue(matrixName); + for(int i = 0; i < 4; ++i) { + for(int j = 0; j < 4; ++j) { + result.set(i, j, obmat.get(j, i).floatValue()); + } + } + return result; + } + + /** + * This method reads animation of the object itself (without bones) and stores it as an ArmatureModifierData + * modifier. The animation is returned as a modifier. It should be later applied regardless other modifiers. The + * reason for this is that object may not have modifiers added but it's animation should be working. + * @param objectStructure + * the structure of the object + * @param dataRepository + * the data repository + * @return animation modifier is returned, it should be separately applied when the object is loaded + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow corrupted + */ + public Modifier readObjectAnimation(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException { + Pointer pIpo = (Pointer)objectStructure.getFieldValue("ipo"); + if(!pIpo.isNull()) { + //check if there is an action name connected with this ipo + String objectAnimationName = null; + List actionBlocks = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); + for(FileBlockHeader actionBlock : actionBlocks) { + Structure action = actionBlock.getStructure(dataRepository); + List actionChannels = ((Structure)action.getFieldValue("chanbase")).evaluateListBase(dataRepository); + if(actionChannels.size() == 1) {//object's animtion action has only one channel + Pointer pChannelIpo = (Pointer)actionChannels.get(0).getFieldValue("ipo"); + if(pChannelIpo.equals(pIpo)) { + objectAnimationName = action.getName(); + break; + } + } + } + + String objectName = objectStructure.getName(); + if(objectAnimationName == null) {//set the object's animation name to object's name + objectAnimationName = objectName; + } + + IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class); + Structure ipoStructure = pIpo.fetchData(dataRepository.getInputStream()).get(0); + Ipo ipo = ipoHelper.createIpo(ipoStructure, dataRepository); + int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, objectAnimationName); + if(animationFrames == null) {//if the name was created here there are no frames set for the animation + animationFrames = new int[] {1, ipo.getLastFrame()}; + } + int fps = dataRepository.getBlenderKey().getFps(); + float start = (float)animationFrames[0] / (float)fps; + float stop = (float)animationFrames[1] / (float)fps; + + //calculating track for the only bone in this skeleton + BoneTrack[] tracks = new BoneTrack[1]; + tracks[0] = ipo.calculateTrack(0, animationFrames[0], animationFrames[1], fps); + + BoneAnimation boneAnimation = new BoneAnimation(objectAnimationName, stop - start); + boneAnimation.setTracks(tracks); + ArrayList animations = new ArrayList(1); + animations.add(boneAnimation); + + //preparing the object's bone + Transform t = this.getTransformation(objectStructure); + Bone bone = new Bone(null); + bone.setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale()); + + return new Modifier(Modifier.ARMATURE_MODIFIER_DATA, new AnimData(new Skeleton(new Bone[] {bone}), animations), null); + } + return null; + } + + @Override + public void clearState() { + fixUpAxis = false; + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ParticlesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ParticlesHelper.java new file mode 100644 index 000000000..d068b06e5 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/ParticlesHelper.java @@ -0,0 +1,118 @@ +package com.jme3.scene.plugins.blender.helpers.v249; + +import java.util.logging.Logger; + +import com.jme3.effect.EmitterMeshConvexHullShape; +import com.jme3.effect.EmitterMeshFaceShape; +import com.jme3.effect.EmitterMeshVertexShape; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.exception.BlenderFileException; +import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.utils.DataRepository; +import com.jme3.scene.plugins.blender.utils.DynamicArray; +import com.jme3.scene.plugins.blender.utils.Pointer; + +public class ParticlesHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName()); + + // part->type + public static final int PART_EMITTER = 0; + public static final int PART_REACTOR = 1; + public static final int PART_HAIR = 2; + public static final int PART_FLUID = 3; + + // part->flag + public static final int PART_REACT_STA_END =1; + public static final int PART_REACT_MULTIPLE =2; + public static final int PART_LOOP =4; + //public static final int PART_LOOP_INSTANT =8; + public static final int PART_HAIR_GEOMETRY =16; + public static final int PART_UNBORN =32; //show unborn particles + public static final int PART_DIED =64; //show died particles + public static final int PART_TRAND =128; + public static final int PART_EDISTR =256; // particle/face from face areas + public static final int PART_STICKY =512; //collided particles can stick to collider + public static final int PART_DIE_ON_COL =1<<12; + public static final int PART_SIZE_DEFL =1<<13; // swept sphere deflections + public static final int PART_ROT_DYN =1<<14; // dynamic rotation + public static final int PART_SIZEMASS =1<<16; + public static final int PART_ABS_LENGTH =1<<15; + public static final int PART_ABS_TIME =1<<17; + public static final int PART_GLOB_TIME =1<<18; + public static final int PART_BOIDS_2D =1<<19; + public static final int PART_BRANCHING =1<<20; + public static final int PART_ANIM_BRANCHING =1<<21; + public static final int PART_SELF_EFFECT =1<<22; + public static final int PART_SYMM_BRANCHING =1<<24; + public static final int PART_HAIR_BSPLINE =1024; + public static final int PART_GRID_INVERT =1<<26; + public static final int PART_CHILD_EFFECT =1<<27; + public static final int PART_CHILD_SEAMS =1<<28; + public static final int PART_CHILD_RENDER =1<<29; + public static final int PART_CHILD_GUIDE =1<<30; + + // part->from + public static final int PART_FROM_VERT =0; + public static final int PART_FROM_FACE =1; + public static final int PART_FROM_VOLUME =2; + public static final int PART_FROM_PARTICLE =3; + public static final int PART_FROM_CHILD =4; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in + * different blender versions. + * @param blenderVersion + * the version read from the blend file + */ + public ParticlesHelper(String blenderVersion) { + super(blenderVersion); + } + + @SuppressWarnings("unchecked") + public ParticleEmitter toParticleEmitter(Structure particleSystem, DataRepository dataRepository) throws BlenderFileException { + ParticleEmitter result = null; + Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part"); + if(!pParticleSettings.isNull()) { + Structure particleSettings = pParticleSettings.fetchData(dataRepository.getInputStream()).get(0); + int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue(); + result = new ParticleEmitter(particleSettings.getName(), Type.Triangle, totPart); + + //setting the emitters shape (the shapes meshes will be set later during modifier applying operation) + int from = ((Number)particleSettings.getFieldValue("from")).intValue(); + switch(from) { + case PART_FROM_VERT: + result.setShape(new EmitterMeshVertexShape()); + break; + case PART_FROM_FACE: + result.setShape(new EmitterMeshFaceShape()); + break; + case PART_FROM_VOLUME: + result.setShape(new EmitterMeshConvexHullShape()); + break; + default: + LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter): " + from); + } + + //reading acceleration + DynamicArray acc = (DynamicArray) particleSettings.getFieldValue("acc"); + result.setInitialVelocity(new Vector3f(acc.get(0).floatValue(), acc.get(1).floatValue(), acc.get(2).floatValue())); + result.setGravity(0);//by default gravity is set to 0.1f so we need to disable it completely + // 2x2 texture animation + result.setImagesX(2); + result.setImagesY(2); + result.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f)); // red + result.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow + result.setStartSize(1.5f); + result.setEndSize(0.1f); + + result.setLowLife(0.5f); + result.setHighLife(3f); + result.setVelocityVariation(0.3f); + } + return result; + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/TextureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/TextureHelper.java new file mode 100644 index 000000000..e2745de6b --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/TextureHelper.java @@ -0,0 +1,1818 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.helpers.v249; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.asset.TextureKey; +import com.jme3.math.FastMath; +import com.jme3.scene.plugins.blender.data.FileBlockHeader; +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.exception.BlenderFileException; +import com.jme3.scene.plugins.blender.helpers.NoiseHelper; +import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.utils.BlenderInputStream; +import com.jme3.scene.plugins.blender.utils.DataRepository; +import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.utils.DynamicArray; +import com.jme3.scene.plugins.blender.utils.Pointer; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.texture.plugins.AWTLoader; +import com.jme3.texture.plugins.DDSLoader; +import com.jme3.texture.plugins.TGALoader; +import com.jme3.util.BufferUtils; + +/** + * A class that is used in texture calculations. + * + * @author Marcin Roguski + */ +public class TextureHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(TextureHelper.class.getName()); + + // texture types + public static final int TEX_NONE = 0; + public static final int TEX_CLOUDS = 1; + public static final int TEX_WOOD = 2; + public static final int TEX_MARBLE = 3; + public static final int TEX_MAGIC = 4; + public static final int TEX_BLEND = 5; + public static final int TEX_STUCCI = 6; + public static final int TEX_NOISE = 7; + public static final int TEX_IMAGE = 8; + public static final int TEX_PLUGIN = 9; + public static final int TEX_ENVMAP = 10; + public static final int TEX_MUSGRAVE = 11; + public static final int TEX_VORONOI = 12; + public static final int TEX_DISTNOISE = 13; + + // mapto + public static final int MAP_COL = 1; + public static final int MAP_NORM = 2; + public static final int MAP_COLSPEC = 4; + public static final int MAP_COLMIR = 8; + public static final int MAP_VARS = 0xFFF0; + public static final int MAP_REF = 16; + public static final int MAP_SPEC = 32; + public static final int MAP_EMIT = 64; + public static final int MAP_ALPHA = 128; + public static final int MAP_HAR = 256; + public static final int MAP_RAYMIRR = 512; + public static final int MAP_TRANSLU = 1024; + public static final int MAP_AMB = 2048; + public static final int MAP_DISPLACE = 4096; + public static final int MAP_WARP = 8192; + public static final int MAP_LAYER = 16384; + + // blendtypes + public static final int MTEX_BLEND = 0; + public static final int MTEX_MUL = 1; + public static final int MTEX_ADD = 2; + public static final int MTEX_SUB = 3; + public static final int MTEX_DIV = 4; + public static final int MTEX_DARK = 5; + public static final int MTEX_DIFF = 6; + public static final int MTEX_LIGHT = 7; + public static final int MTEX_SCREEN = 8; + public static final int MTEX_OVERLAY = 9; + public static final int MTEX_BLEND_HUE = 10; + public static final int MTEX_BLEND_SAT = 11; + public static final int MTEX_BLEND_VAL = 12; + public static final int MTEX_BLEND_COLOR = 13; + public static final int MTEX_NUM_BLENDTYPES = 14; + + // variables used in rampBlend method + public static final int MA_RAMP_BLEND = 0; + public static final int MA_RAMP_ADD = 1; + public static final int MA_RAMP_MULT = 2; + public static final int MA_RAMP_SUB = 3; + public static final int MA_RAMP_SCREEN = 4; + public static final int MA_RAMP_DIV = 5; + public static final int MA_RAMP_DIFF = 6; + public static final int MA_RAMP_DARK = 7; + public static final int MA_RAMP_LIGHT = 8; + public static final int MA_RAMP_OVERLAY = 9; + public static final int MA_RAMP_DODGE = 10; + public static final int MA_RAMP_BURN = 11; + public static final int MA_RAMP_HUE = 12; + public static final int MA_RAMP_SAT = 13; + public static final int MA_RAMP_VAL = 14; + public static final int MA_RAMP_COLOR = 15; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender + * versions. + * + * @param blenderVersion + * the version read from the blend file + */ + public TextureHelper(String blenderVersion) { + super(blenderVersion); + } + + /** + * This class returns a texture read from the file or from packed blender data. The returned texture has the name set to the value of + * its blender type. + * + * @param tex + * texture structure filled with data + * @param dataRepository + * the data repository + * @return the texture that can be used by JME engine + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + public Texture getTexture(Structure tex, DataRepository dataRepository) throws BlenderFileException { + Texture result = (Texture) dataRepository.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (result != null) { + return result; + } + int type = ((Number) tex.getFieldValue("type")).intValue(); + int width = dataRepository.getBlenderKey().getGeneratedTextureWidth(); + int height = dataRepository.getBlenderKey().getGeneratedTextureHeight(); + + switch (type) { + case TEX_NONE:// No texture, do nothing + break; + case TEX_IMAGE:// (it is first because probably this will be most commonly used) + Pointer pImage = (Pointer) tex.getFieldValue("ima"); + Structure image = pImage.fetchData(dataRepository.getInputStream()).get(0); + result = this.getTextureFromImage(image, dataRepository); + break; + case TEX_CLOUDS: + result = this.clouds(tex, width, height, dataRepository); + break; + case TEX_WOOD: + result = this.wood(tex, width, height, dataRepository); + break; + case TEX_MARBLE: + result = this.marble(tex, width, height, dataRepository); + break; + case TEX_MAGIC: + result = this.magic(tex, width, height, dataRepository); + break; + case TEX_BLEND: + result = this.blend(tex, width, height, dataRepository); + break; + case TEX_STUCCI: + result = this.stucci(tex, width, height, dataRepository); + break; + case TEX_NOISE: + result = this.texnoise(tex, width, height, dataRepository); + break; + case TEX_MUSGRAVE: + result = this.musgrave(tex, width, height, dataRepository); + break; + case TEX_VORONOI: + result = this.voronoi(tex, width, height, dataRepository); + break; + case TEX_DISTNOISE: + result = this.distnoise(tex, width, height, dataRepository); + break; + case TEX_PLUGIN: + case TEX_ENVMAP:// TODO: implement envmap texture + LOGGER.log(Level.WARNING, "Unsupported texture type: " + type + " for texture: " + tex.getName()); + break; + default: + throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName()); + } + if (result != null) { + result.setName(String.valueOf(type)); + result.setWrap(WrapMode.Repeat); + } + return result; + } + + /** + * This method generates the clouds texture. The result is one pixel. + * + * @param tex + * the texture structure + * @param width + * the width of texture (in pixels) + * @param height + * the height of texture (in pixels) + * @param dataRepository + * the data repository + * @return generated texture + */ + protected Texture clouds(Structure tex, int width, int height, DataRepository dataRepository) { + // preparing the proper data + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + float wDelta = 1.0f / width, hDelta = 1.0f / height; + float[] texvec = new float[] { 0, 0, 0 }; + TexResult texres = new TexResult(); + + // reading the data from the texture structure + float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + int noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + int noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + int noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); + float contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + float bright = ((Number) tex.getFieldValue("bright")).floatValue(); + boolean isHard = noiseType != com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_NOISESOFT; + int sType = ((Number) tex.getFieldValue("stype")).intValue(); + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = sType == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_COLOR || colorBand != null ? Format.RGB8 + : Format.Luminance8; + int bytesPerPixel = sType == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_COLOR || colorBand != null ? 3 : 1; + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i;// x + for (int j = -halfH; j < halfH; ++j) { + texvec[1] = hDelta * j;// y (z is always = 0) + + texres.tin = noiseHelper.bliGTurbulence(noisesize, texvec[0], texvec[1], texvec[2], noiseDepth, isHard, noiseBasis); + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + if (texres.nor != null) { + float nabla = ((Number) tex.getFieldValue("nabla")).floatValue(); + // calculate bumpnormal + texres.nor[0] = noiseHelper.bliGTurbulence(noisesize, texvec[0] + nabla, texvec[1], texvec[2], noiseDepth, isHard, + noiseBasis); + texres.nor[1] = noiseHelper.bliGTurbulence(noisesize, texvec[0], texvec[1] + nabla, texvec[2], noiseDepth, isHard, + noiseBasis); + texres.nor[2] = noiseHelper.bliGTurbulence(noisesize, texvec[0], texvec[1], texvec[2] + nabla, noiseDepth, isHard, + noiseBasis); + noiseHelper.texNormalDerivate(colorBand, texres, dataRepository); + } + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else if (sType == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_COLOR) { + // in this case, int. value should really be computed from color, + // and bumpnormal from that, would be too slow, looks ok as is + texres.tr = texres.tin; + texres.tg = noiseHelper.bliGTurbulence(noisesize, texvec[1], texvec[0], texvec[2], noiseDepth, isHard, noiseBasis); + texres.tb = noiseHelper.bliGTurbulence(noisesize, texvec[1], texvec[2], texvec[0], noiseDepth, isHard, noiseBasis); + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + noiseHelper.brightnesAndContrast(texres, contrast, bright); + data.put((byte) (texres.tin * 255)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method generates the wood texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + protected Texture wood(Structure tex, int width, int height, DataRepository dataRepository) { + // preparing the proper data + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + float contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + float bright = ((Number) tex.getFieldValue("bright")).floatValue(); + float nabla = ((Number) tex.getFieldValue("nabla")).floatValue(); + float wDelta = 1.0f / width, hDelta = 1.0f / height; + float[] texvec = new float[] { 0, 0, 0 }; + TexResult texres = new TexResult(); + int halfW = width; + int halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = colorBand != null ? Format.RGB8 : Format.Luminance8; + int bytesPerPixel = colorBand != null ? 3 : 1; + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i; + for (int j = -halfH; j < halfH; ++j) { + texvec[1] = hDelta * j; + texres.tin = noiseHelper.woodInt(tex, texvec[0], texvec[1], texvec[2], dataRepository); + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + if (texres.nor != null) {// calculate bumpnormal + texres.nor[0] = noiseHelper.woodInt(tex, texvec[0] + nabla, texvec[1], texvec[2], dataRepository); + texres.nor[1] = noiseHelper.woodInt(tex, texvec[0], texvec[1] + nabla, texvec[2], dataRepository); + texres.nor[2] = noiseHelper.woodInt(tex, texvec[0], texvec[1], texvec[2] + nabla, dataRepository); + noiseHelper.texNormalDerivate(colorBand, texres, dataRepository); + } + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + noiseHelper.brightnesAndContrast(texres, contrast, bright); + data.put((byte) (texres.tin * 255)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method generates the marble texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + protected Texture marble(Structure tex, int width, int height, DataRepository dataRepository) { + // preparing the proper data + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + float contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + float bright = ((Number) tex.getFieldValue("bright")).floatValue(); + float nabla = ((Number) tex.getFieldValue("nabla")).floatValue(); + float wDelta = 1.0f / width, hDelta = 1.0f / height; + float[] texvec = new float[] { 0, 0, 0 }; + TexResult texres = new TexResult(); + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = colorBand != null ? Format.RGB8 : Format.Luminance8; + int bytesPerPixel = colorBand != null ? 3 : 1; + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i; + for (int j = -halfH; j < halfH; ++j) { + texvec[1] = hDelta * j; + texres.tin = noiseHelper.marbleInt(tex, texvec[0], texvec[1], texvec[2], dataRepository); + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + if (texres.nor != null) {// calculate bumpnormal + texres.nor[0] = noiseHelper.marbleInt(tex, texvec[0] + nabla, texvec[1], texvec[2], dataRepository); + texres.nor[1] = noiseHelper.marbleInt(tex, texvec[0], texvec[1] + nabla, texvec[2], dataRepository); + texres.nor[2] = noiseHelper.marbleInt(tex, texvec[0], texvec[1], texvec[2] + nabla, dataRepository); + noiseHelper.texNormalDerivate(colorBand, texres, dataRepository); + } + + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + noiseHelper.brightnesAndContrast(texres, contrast, bright); + data.put((byte) (texres.tin * 255.0f)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method generates the magic texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + protected Texture magic(Structure tex, int width, int height, DataRepository dataRepository) { + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + float x, y, z, turb; + int noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + float turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f; + float[] texvec = new float[] { 0, 0, 0 }; + TexResult texres = new TexResult(); + float wDelta = 1.0f / width, hDelta = 1.0f / height; + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * 4); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i; + for (int j = -halfH; j < halfH; ++j) { + turb = turbul; + texvec[1] = hDelta * j; + x = (float) Math.sin((texvec[0] + texvec[1]) * 5.0f);// in blender: Math.sin((texvec[0] + texvec[1] + texvec[2]) * 5.0f); + y = (float) Math.cos((-texvec[0] + texvec[1]) * 5.0f);// in blender: Math.cos((-texvec[0] + texvec[1] - texvec[2]) * 5.0f); + z = -(float) Math.cos((-texvec[0] - texvec[1]) * 5.0f);// in blender: Math.cos((-texvec[0] - texvec[1] + texvec[2]) * 5.0f); + + if (colorBand != null) { + texres.tin = 0.3333f * (x + y + z); + noiseHelper.doColorband(colorBand, texres, dataRepository); + } else { + if (noisedepth > 0) { + x *= turb; + y *= turb; + z *= turb; + y = -(float) Math.cos(x - y + z) * turb; + if (noisedepth > 1) { + x = (float) Math.cos(x - y - z) * turb; + if (noisedepth > 2) { + z = (float) Math.sin(-x - y - z) * turb; + if (noisedepth > 3) { + x = -(float) Math.cos(-x + y - z) * turb; + if (noisedepth > 4) { + y = -(float) Math.sin(-x + y + z) * turb; + if (noisedepth > 5) { + y = -(float) Math.cos(-x + y + z) * turb; + if (noisedepth > 6) { + x = (float) Math.cos(x + y + z) * turb; + if (noisedepth > 7) { + z = (float) Math.sin(x + y - z) * turb; + if (noisedepth > 8) { + x = -(float) Math.cos(-x - y + z) * turb; + if (noisedepth > 9) { + y = -(float) Math.sin(x - y + z) * turb; + } + } + } + } + } + } + } + } + } + } + + if (turb != 0.0f) { + turb *= 2.0f; + x /= turb; + y /= turb; + z /= turb; + } + texres.tr = 0.5f - x; + texres.tg = 0.5f - y; + texres.tb = 0.5f - z; + } + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tin * 255)); + data.put((byte) (texres.tb * 255)); + data.put((byte) (texres.tg * 255)); + data.put((byte) (texres.tr * 255)); + } + } + return new Texture2D(new Image(Format.ABGR8, width, height, data)); + } + + /** + * This method generates the blend texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + protected Texture blend(Structure tex, int width, int height, DataRepository dataRepository) { + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + int flag = ((Number) tex.getFieldValue("flag")).intValue(); + int stype = ((Number) tex.getFieldValue("stype")).intValue(); + float contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + float brightness = ((Number) tex.getFieldValue("bright")).floatValue(); + float wDelta = 1.0f / width, hDelta = 1.0f / height, x, y, t; + float[] texvec = new float[] { 0, 0, 0 }; + TexResult texres = new TexResult(); + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = colorBand != null ? Format.RGB8 : Format.Luminance8; + int bytesPerPixel = colorBand != null ? 3 : 1; + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i; + for (int j = -halfH; j < halfH; ++j) { + texvec[1] = hDelta * j; + if ((flag & com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_FLIPBLEND) != 0) { + x = texvec[1]; + y = texvec[0]; + } else { + x = texvec[0]; + y = texvec[1]; + } + + if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_LIN) { /* lin */ + texres.tin = (1.0f + x) / 2.0f; + } else if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_QUAD) { /* quad */ + texres.tin = (1.0f + x) / 2.0f; + if (texres.tin < 0.0f) { + texres.tin = 0.0f; + } else { + texres.tin *= texres.tin; + } + } else if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_EASE) { /* ease */ + texres.tin = (1.0f + x) / 2.0f; + if (texres.tin <= 0.0f) { + texres.tin = 0.0f; + } else if (texres.tin >= 1.0f) { + texres.tin = 1.0f; + } else { + t = texres.tin * texres.tin; + texres.tin = 3.0f * t - 2.0f * t * texres.tin; + } + } else if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_DIAG) { /* diag */ + texres.tin = (2.0f + x + y) / 4.0f; + } else if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_RAD) { /* radial */ + texres.tin = (float) Math.atan2(y, x) / FastMath.TWO_PI + 0.5f; + } else { /* sphere TEX_SPHERE */ + texres.tin = 1.0f - (float) Math.sqrt(x * x + y * y + texvec[2] * texvec[2]); + if (texres.tin < 0.0f) { + texres.tin = 0.0f; + } + if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_HALO) { + texres.tin *= texres.tin; + } /* halo */ + } + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + noiseHelper.brightnesAndContrast(texres, contrast, brightness); + data.put((byte) (texres.tin * 255.0f)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method generates the stucci texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + protected Texture stucci(Structure tex, int width, int height, DataRepository dataRepository) { + float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); + float turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); + boolean isHard = noisetype != com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_NOISESOFT; + int stype = ((Number) tex.getFieldValue("stype")).intValue(); + + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + float[] texvec = new float[] { 0, 0, 0 }; + TexResult texres = new TexResult(); + float wDelta = 1.0f / width, hDelta = 1.0f / height, b2, ofs; + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = colorBand != null ? Format.RGB8 : Format.Luminance8; + int bytesPerPixel = colorBand != null ? 3 : 1; + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i;// x + for (int j = -halfH; j < halfH; ++j) { + texvec[1] = hDelta * j;// y (z is always = 0) + b2 = noiseHelper.bliGNoise(noisesize, texvec[0], texvec[1], texvec[2], isHard, noisebasis); + + ofs = turbul / 200.0f; + + if (stype != 0) { + ofs *= b2 * b2; + } + + texres.tin = noiseHelper.bliGNoise(noisesize, texvec[0], texvec[1], texvec[2] + ofs, isHard, noisebasis);// ==nor[2] + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + if (texres.nor != null) { + texres.nor[0] = noiseHelper.bliGNoise(noisesize, texvec[0] + ofs, texvec[1], texvec[2], isHard, noisebasis); + texres.nor[1] = noiseHelper.bliGNoise(noisesize, texvec[0], texvec[1] + ofs, texvec[2], isHard, noisebasis); + texres.nor[2] = texres.tin; + noiseHelper.texNormalDerivate(colorBand, texres, dataRepository); + + if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_WALLOUT) { + texres.nor[0] = -texres.nor[0]; + texres.nor[1] = -texres.nor[1]; + texres.nor[2] = -texres.nor[2]; + } + } + } + + if (stype == com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_WALLOUT) { + texres.tin = 1.0f - texres.tin; + } + if (texres.tin < 0.0f) { + texres.tin = 0.0f; + } + if (colorBand != null) { + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + data.put((byte) (texres.tin * 255.0f)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method generates the noise texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + // TODO: correct this one, so it looks more like the texture generated by blender + protected Texture texnoise(Structure tex, int width, int height, DataRepository dataRepository) { + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + float div = 3.0f; + int val, ran, loop; + int noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + float contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + float brightness = ((Number) tex.getFieldValue("bright")).floatValue(); + TexResult texres = new TexResult(); + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = colorBand != null ? Format.RGB8 : Format.Luminance8; + int bytesPerPixel = colorBand != null ? 3 : 1; + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + for (int j = -halfH; j < halfH; ++j) { + ran = FastMath.rand.nextInt();// BLI_rand(); + val = ran & 3; + + loop = noisedepth; + while (loop-- != 0) { + ran = ran >> 2; + val *= ran & 3; + div *= 3.0f; + } + texres.tin = val;// / div; + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + noiseHelper.brightnesAndContrast(texres, contrast, brightness); + data.put((byte) (texres.tin * 255.0f)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method generates the musgrave texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + protected Texture musgrave(Structure tex, int width, int height, DataRepository dataRepository) { + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + int stype = ((Number) tex.getFieldValue("stype")).intValue(); + float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + TexResult texres = new TexResult(); + float[] texvec = new float[] { 0, 0, 0 }; + float wDelta = 1.0f / width, hDelta = 1.0f / height; + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = colorBand != null ? Format.RGB8 : Format.Luminance8; + int bytesPerPixel = colorBand != null ? 3 : 1; + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i / noisesize; + for (int j = -halfH; j < halfH; ++j) { + texvec[1] = hDelta * j / noisesize; + switch (stype) { + case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_MFRACTAL: + case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_FBM: + noiseHelper.mgMFractalOrfBmTex(tex, texvec, colorBand, texres, dataRepository); + break; + case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_RIDGEDMF: + case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_HYBRIDMF: + noiseHelper.mgRidgedOrHybridMFTex(tex, texvec, colorBand, texres, dataRepository); + break; + case com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_HTERRAIN: + noiseHelper.mgHTerrainTex(tex, texvec, colorBand, texres, dataRepository); + break; + default: + throw new IllegalStateException("Unknown type of musgrave texture: " + stype); + } + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + data.put((byte) (texres.tin * 255.0f)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method generates the voronoi texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + protected Texture voronoi(Structure tex, int width, int height, DataRepository dataRepository) { + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + float vn_w1 = ((Number) tex.getFieldValue("vn_w1")).floatValue(); + float vn_w2 = ((Number) tex.getFieldValue("vn_w2")).floatValue(); + float vn_w3 = ((Number) tex.getFieldValue("vn_w3")).floatValue(); + float vn_w4 = ((Number) tex.getFieldValue("vn_w4")).floatValue(); + float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + float nabla = ((Number) tex.getFieldValue("nabla")).floatValue(); + float ns_outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue(); + float vn_mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue(); + int vn_distm = ((Number) tex.getFieldValue("vn_distm")).intValue(); + int vn_coltype = ((Number) tex.getFieldValue("vn_coltype")).intValue(); + float contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + float brightness = ((Number) tex.getFieldValue("bright")).floatValue(); + + TexResult texres = new TexResult(); + float[] texvec = new float[] { 0, 0, 0 }; + float wDelta = 1.0f / width, hDelta = 1.0f / height; + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = vn_coltype != 0 || colorBand != null ? Format.RGB8 : Format.Luminance8; + int bytesPerPixel = vn_coltype != 0 || colorBand != null ? 3 : 1; + + float[] da = new float[4], pa = new float[12]; /* distance and point coordinate arrays of 4 nearest neighbours */ + float[] ca = vn_coltype != 0 ? new float[3] : null; // cell color + float aw1 = FastMath.abs(vn_w1); + float aw2 = FastMath.abs(vn_w2); + float aw3 = FastMath.abs(vn_w3); + float aw4 = FastMath.abs(vn_w4); + float sc = aw1 + aw2 + aw3 + aw4; + if (sc != 0.f) { + sc = ns_outscale / sc; + } + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i / noisesize; + for (int j = -halfH; j < halfH; ++j) { + texvec[1] = hDelta * j / noisesize; + + noiseHelper.voronoi(texvec[0], texvec[1], texvec[2], da, pa, vn_mexp, vn_distm); + texres.tin = sc * FastMath.abs(vn_w1 * da[0] + vn_w2 * da[1] + vn_w3 * da[2] + vn_w4 * da[3]); + if (vn_coltype != 0) { + noiseHelper.cellNoiseV(pa[0], pa[1], pa[2], ca); + texres.tr = aw1 * ca[0]; + texres.tg = aw1 * ca[1]; + texres.tb = aw1 * ca[2]; + noiseHelper.cellNoiseV(pa[3], pa[4], pa[5], ca); + texres.tr += aw2 * ca[0]; + texres.tg += aw2 * ca[1]; + texres.tb += aw2 * ca[2]; + noiseHelper.cellNoiseV(pa[6], pa[7], pa[8], ca); + texres.tr += aw3 * ca[0]; + texres.tg += aw3 * ca[1]; + texres.tb += aw3 * ca[2]; + noiseHelper.cellNoiseV(pa[9], pa[10], pa[11], ca); + texres.tr += aw4 * ca[0]; + texres.tg += aw4 * ca[1]; + texres.tb += aw4 * ca[2]; + if (vn_coltype >= 2) { + float t1 = (da[1] - da[0]) * 10.0f; + if (t1 > 1) { + t1 = 1.0f; + } + if (vn_coltype == 3) { + t1 *= texres.tin; + } else { + t1 *= sc; + } + texres.tr *= t1; + texres.tg *= t1; + texres.tb *= t1; + } else { + texres.tr *= sc; + texres.tg *= sc; + texres.tb *= sc; + } + } + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + if (texres.nor != null) { + float offs = nabla / noisesize; // also scaling of texvec + // calculate bumpnormal + noiseHelper.voronoi(texvec[0] + offs, texvec[1], texvec[2], da, pa, vn_mexp, vn_distm); + texres.nor[0] = sc * FastMath.abs(vn_w1 * da[0] + vn_w2 * da[1] + vn_w3 * da[2] + vn_w4 * da[3]); + noiseHelper.voronoi(texvec[0], texvec[1] + offs, texvec[2], da, pa, vn_mexp, vn_distm); + texres.nor[1] = sc * FastMath.abs(vn_w1 * da[0] + vn_w2 * da[1] + vn_w3 * da[2] + vn_w4 * da[3]); + noiseHelper.voronoi(texvec[0], texvec[1], texvec[2] + offs, da, pa, vn_mexp, vn_distm); + texres.nor[2] = sc * FastMath.abs(vn_w1 * da[0] + vn_w2 * da[1] + vn_w3 * da[2] + vn_w4 * da[3]); + noiseHelper.texNormalDerivate(colorBand, texres, dataRepository); + } + } + + if (vn_coltype != 0 || colorBand != null) { + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tr * 255.0f));// tin or tr?? + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + noiseHelper.brightnesAndContrast(texres, contrast, brightness); + data.put((byte) (texres.tin * 255.0f)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method generates the distorted noise texture. + * + * @param tex + * the texture structure + * @param width + * the width of the texture + * @param height + * the height of the texture + * @param dataRepository + * the data repository + * @return the generated texture + */ + protected Texture distnoise(Structure tex, int width, int height, DataRepository dataRepository) { + NoiseHelper noiseHelper = dataRepository.getHelper(NoiseHelper.class); + float noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + float nabla = ((Number) tex.getFieldValue("nabla")).floatValue(); + float distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue(); + int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + int noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue(); + float contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + float brightness = ((Number) tex.getFieldValue("bright")).floatValue(); + + TexResult texres = new TexResult(); + float[] texvec = new float[] { 0, 0, 0 }; + float wDelta = 1.0f / width, hDelta = 1.0f / height; + int halfW = width, halfH = height; + width <<= 1; + height <<= 1; + ColorBand colorBand = this.readColorband(tex, dataRepository); + Format format = colorBand != null ? Format.RGB8 : Format.Luminance8; + int bytesPerPixel = colorBand != null ? 3 : 1; + + ByteBuffer data = BufferUtils.createByteBuffer(width * height * bytesPerPixel); + for (int i = -halfW; i < halfW; ++i) { + texvec[0] = wDelta * i / noisesize; + for (int j = -halfH; j < halfH; ++j) { + texvec[1] = hDelta * j / noisesize; + + texres.tin = noiseHelper.mgVLNoise(texvec[0], texvec[1], texvec[2], distAmount, noisebasis, noisebasis2); + if (colorBand != null) { + noiseHelper.doColorband(colorBand, texres, dataRepository); + if (texres.nor != null) { + float offs = nabla / noisesize; // also scaling of texvec + /* calculate bumpnormal */ + texres.nor[0] = noiseHelper.mgVLNoise(texvec[0] + offs, texvec[1], texvec[2], distAmount, noisebasis, noisebasis2); + texres.nor[1] = noiseHelper.mgVLNoise(texvec[0], texvec[1] + offs, texvec[2], distAmount, noisebasis, noisebasis2); + texres.nor[2] = noiseHelper.mgVLNoise(texvec[0], texvec[1], texvec[2] + offs, distAmount, noisebasis, noisebasis2); + noiseHelper.texNormalDerivate(colorBand, texres, dataRepository); + } + + noiseHelper.brightnesAndContrastRGB(tex, texres); + data.put((byte) (texres.tr * 255.0f)); + data.put((byte) (texres.tg * 255.0f)); + data.put((byte) (texres.tb * 255.0f)); + } else { + noiseHelper.brightnesAndContrast(texres, contrast, brightness); + data.put((byte) (texres.tin * 255.0f)); + } + } + } + return new Texture2D(new Image(format, width, height, data)); + } + + /** + * This method reads the colorband data from the given texture structure. + * + * @param tex + * the texture structure + * @param dataRepository + * the data repository + * @return read colorband or null if not present + */ + protected ColorBand readColorband(Structure tex, DataRepository dataRepository) { + ColorBand result = null; + int flag = ((Number) tex.getFieldValue("flag")).intValue(); + if ((flag & com.jme3.scene.plugins.blender.helpers.v249.NoiseHelper.TEX_COLORBAND) != 0) { + Pointer pColorband = (Pointer) tex.getFieldValue("coba"); + Structure colorbandStructure; + try { + colorbandStructure = pColorband.fetchData(dataRepository.getInputStream()).get(0); + result = new ColorBand(colorbandStructure); + } catch (BlenderFileException e) { + LOGGER.warning("Cannot fetch the colorband structure. The reason: " + e.getLocalizedMessage()); + // TODO: throw an exception here ??? + } + } + return result; + } + + /** + * This method blends the given texture with material color and the defined color in 'map to' panel. As a result of this method a new + * texture is created. The input texture is NOT modified. + * + * @param materialColor + * the material diffuse color + * @param texture + * the texture we use in blending + * @param color + * the color defined for the texture + * @param affectFactor + * the factor that the color affects the texture (value form 0.0 to 1.0) + * @param blendType + * the blending type + * @param dataRepository + * the data repository + * @return new texture that was created after the blending + */ + public Texture blendTexture(float[] materialColor, Texture texture, float[] color, float affectFactor, + int blendType, boolean neg, + DataRepository dataRepository) { + Format format = texture.getImage().getFormat(); + ByteBuffer data = texture.getImage().getData(0); + data.rewind(); + int width = texture.getImage().getWidth(); + int height = texture.getImage().getHeight(); + ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 3); + + float[] resultPixel = new float[3]; + float[] texPixel = new float[3]; + int dataIndex = 0; + + while (data.hasRemaining()) { + byte pixelValue = data.get(); + texPixel[0] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f; + if(neg) { + texPixel[0] = 1.0f - texPixel[0]; + } + if (format == Format.ABGR8) { + //intensity not used at the moment + pixelValue = data.get(); + texPixel[2] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f; + if(neg) { + texPixel[2] = 1.0f - texPixel[2]; + } + pixelValue = data.get(); + texPixel[1] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f; + if(neg) { + texPixel[1] = 1.0f - texPixel[1]; + } + pixelValue = data.get(); + texPixel[0] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f; + if(neg) { + texPixel[0] = 1.0f - texPixel[0]; + } + + this.blendPixel(resultPixel, materialColor, texPixel, 1.0f, affectFactor, blendType, dataRepository); + + newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); + } else if (format == Format.RGB8) { + pixelValue = data.get(); + texPixel[1] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f; + if(neg) { + texPixel[1] = 1.0f - texPixel[1]; + } + pixelValue = data.get(); + texPixel[2] = pixelValue >= 0 ? 1.0f - pixelValue / 255.0f : (~pixelValue + 1) / 255.0f; + if(neg) { + texPixel[2] = 1.0f - texPixel[2]; + } + float tin = texPixel[0];//(texPixel[0] + texPixel[1] + texPixel[2]) / 3.0f; + this.blendPixel(resultPixel, texPixel, color, tin, affectFactor, blendType, dataRepository); + newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); + } else if (format == Format.Luminance8) { + this.blendPixel(resultPixel, materialColor, color, texPixel[0], affectFactor, blendType, dataRepository); + newData.put((byte) (resultPixel[0] * 255.0f)); + newData.put((byte) (resultPixel[1] * 255.0f)); + newData.put((byte) (resultPixel[2] * 255.0f)); + } else { + throw new IllegalStateException("Invalid texture format for blending operation: " + format); + } + } + return new Texture2D(new Image(Format.RGB8, width, height, newData)); + } + + /** + * This method blends the texture with an appropriate color. + * + * @param result + * the result color (variable 'in' in blender source code) + * @param materialColor + * the texture color (variable 'out' in blender source coude) + * @param color + * the previous color (variable 'tex' in blender source code) + * @param textureIntensity + * texture intensity (variable 'fact' in blender source code) + * @param textureFactor + * texture affection factor (variable 'facg' in blender source code) + * @param blendtype + * the blend type + * @param dataRepository + * the data repository + */ + public void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, + int blendtype, DataRepository dataRepository) { + float facm, col; + + switch (blendtype) { + case MTEX_BLEND: + textureIntensity *= textureFactor; + facm = 1.0f - textureIntensity; + result[0] = textureIntensity * color[0] + facm * materialColor[0]; + result[1] = textureIntensity * color[1] + facm * materialColor[1]; + result[2] = textureIntensity * color[2] + facm * materialColor[2]; + break; + case MTEX_MUL: + textureIntensity *= textureFactor; + facm = 1.0f - textureFactor; + result[0] = (facm + textureIntensity * materialColor[0]) * color[0]; + result[1] = (facm + textureIntensity * materialColor[1]) * color[1]; + result[2] = (facm + textureIntensity * materialColor[2]) * color[2]; + break; + case MTEX_DIV: + textureIntensity *= textureFactor; + facm = 1.0f - textureIntensity; + if (color[0] != 0.0) { + result[0] = (facm * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f; + } + if (color[1] != 0.0) { + result[1] = (facm * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f; + } + if (color[2] != 0.0) { + result[2] = (facm * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f; + } + break; + case MTEX_SCREEN: + textureIntensity *= textureFactor; + facm = 1.0f - textureFactor; + result[0] = 1.0f - (facm + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); + result[1] = 1.0f - (facm + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); + result[2] = 1.0f - (facm + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); + break; + case MTEX_OVERLAY: + textureIntensity *= textureFactor; + facm = 1.0f - textureFactor; + if (materialColor[0] < 0.5f) { + result[0] = color[0] * (facm + 2.0f * textureIntensity * materialColor[0]); + } else { + result[0] = 1.0f - (facm + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); + } + if (materialColor[1] < 0.5f) { + result[1] = color[1] * (facm + 2.0f * textureIntensity * materialColor[1]); + } else { + result[1] = 1.0f - (facm + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); + } + if (materialColor[2] < 0.5f) { + result[2] = color[2] * (facm + 2.0f * textureIntensity * materialColor[2]); + } else { + result[2] = 1.0f - (facm + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); + } + break; + case MTEX_SUB: + textureIntensity *= textureFactor; + result[0] = materialColor[0] - textureIntensity * color[0]; + result[1] = materialColor[1] - textureIntensity * color[1]; + result[2] = materialColor[2] - textureIntensity * color[2]; + result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); + result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); + result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); + break; + case MTEX_ADD: + textureIntensity *= textureFactor; + result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f; + result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f; + result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f; + break; + case MTEX_DIFF: + textureIntensity *= textureFactor; + facm = 1.0f - textureIntensity; + result[0] = facm * color[0] + textureIntensity * Math.abs(materialColor[0] - color[0]); + result[1] = facm * color[1] + textureIntensity * Math.abs(materialColor[1] - color[1]); + result[2] = facm * color[2] + textureIntensity * Math.abs(materialColor[2] - color[2]); + break; + case MTEX_DARK: + textureIntensity *= textureFactor; + col = textureIntensity * color[0]; + result[0] = col < materialColor[0] ? col : materialColor[0]; + col = textureIntensity * color[1]; + result[1] = col < materialColor[1] ? col : materialColor[1]; + col = textureIntensity * color[2]; + result[2] = col < materialColor[2] ? col : materialColor[2]; + break; + case MTEX_LIGHT: + textureIntensity *= textureFactor; + col = textureIntensity * color[0]; + result[0] = col > materialColor[0] ? col : materialColor[0]; + col = textureIntensity * color[1]; + result[1] = col > materialColor[1] ? col : materialColor[1]; + col = textureIntensity * color[2]; + result[2] = col > materialColor[2] ? col : materialColor[2]; + break; + case MTEX_BLEND_HUE: + textureIntensity *= textureFactor; + System.arraycopy(materialColor, 0, result, 0, 3); + this.rampBlend(MA_RAMP_HUE, result, textureIntensity, color, dataRepository); + break; + case MTEX_BLEND_SAT: + textureIntensity *= textureFactor; + System.arraycopy(materialColor, 0, result, 0, 3); + this.rampBlend(MA_RAMP_SAT, result, textureIntensity, color, dataRepository); + break; + case MTEX_BLEND_VAL: + textureIntensity *= textureFactor; + System.arraycopy(materialColor, 0, result, 0, 3); + this.rampBlend(MA_RAMP_VAL, result, textureIntensity, color, dataRepository); + break; + case MTEX_BLEND_COLOR: + textureIntensity *= textureFactor; + System.arraycopy(materialColor, 0, result, 0, 3); + this.rampBlend(MA_RAMP_COLOR, result, textureIntensity, color, dataRepository); + break; + default: + throw new IllegalStateException("Unknown blend type: " + blendtype); + } + } + + /** + * The method that performs the ramp blending (whatever it is :P - copied from blender sources). + * + * @param type + * the ramp type + * @param rgb + * the rgb value where the result is stored + * @param fac + * color affection factor + * @param col + * the texture color + * @param dataRepository + * the data repository + */ + public void rampBlend(int type, float[] rgb, float fac, float[] col, DataRepository dataRepository) { + float tmp, facm = 1.0f - fac; + MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); + + switch (type) { + case MA_RAMP_HUE: + if (rgb.length == 3) { + float[] colorTransformResult = new float[3]; + materialHelper.rgbToHsv(col[0], col[1], col[2], colorTransformResult); + if (colorTransformResult[1] != 0.0f) { + float colH = colorTransformResult[0]; + materialHelper.rgbToHsv(rgb[0], rgb[1], rgb[2], colorTransformResult); + materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult); + rgb[0] = facm * rgb[0] + fac * colorTransformResult[0]; + rgb[1] = facm * rgb[1] + fac * colorTransformResult[1]; + rgb[2] = facm * rgb[2] + fac * colorTransformResult[2]; + } + } + break; + case MA_RAMP_SAT: + if (rgb.length == 3) { + float[] colorTransformResult = new float[3]; + materialHelper.rgbToHsv(rgb[0], rgb[1], rgb[2], colorTransformResult); + float rH = colorTransformResult[0]; + float rS = colorTransformResult[1]; + float rV = colorTransformResult[2]; + if (rS != 0) { + materialHelper.rgbToHsv(col[0], col[1], col[2], colorTransformResult); + materialHelper.hsvToRgb(rH, (facm * rS + fac * colorTransformResult[1]), rV, rgb); + } + } + break; + case MA_RAMP_VAL: + if (rgb.length == 3) { + float[] rgbToHsv = new float[3]; + float[] colToHsv = new float[3]; + materialHelper.rgbToHsv(rgb[0], rgb[1], rgb[2], rgbToHsv); + materialHelper.rgbToHsv(col[0], col[1], col[2], colToHsv); + materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], (facm * rgbToHsv[2] + fac * colToHsv[2]), rgb); + } + break; + case MA_RAMP_COLOR: + if (rgb.length == 3) { + float[] rgbToHsv = new float[3]; + float[] colToHsv = new float[3]; + materialHelper.rgbToHsv(col[0], col[1], col[2], colToHsv); + if (colToHsv[2] != 0) { + materialHelper.rgbToHsv(rgb[0], rgb[1], rgb[2], rgbToHsv); + materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv); + rgb[0] = facm * rgb[0] + fac * rgbToHsv[0]; + rgb[1] = facm * rgb[1] + fac * rgbToHsv[1]; + rgb[2] = facm * rgb[2] + fac * rgbToHsv[2]; + } + } + break; + case MA_RAMP_BLEND: + rgb[0] = facm * rgb[0] + fac * col[0]; + if (rgb.length == 3) { + rgb[1] = facm * rgb[1] + fac * col[1]; + rgb[2] = facm * rgb[2] + fac * col[2]; + } + break; + case MA_RAMP_ADD: + rgb[0] += fac * col[0]; + if (rgb.length == 3) { + rgb[1] += fac * col[1]; + rgb[2] += fac * col[2]; + } + break; + case MA_RAMP_MULT: + rgb[0] *= facm + fac * col[0]; + if (rgb.length == 3) { + rgb[1] *= facm + fac * col[1]; + rgb[2] *= facm + fac * col[2]; + } + break; + case MA_RAMP_SCREEN: + rgb[0] = 1.0f - (facm + fac * (1.0f - col[0])) * (1.0f - rgb[0]); + if (rgb.length == 3) { + rgb[1] = 1.0f - (facm + fac * (1.0f - col[1])) * (1.0f - rgb[1]); + rgb[2] = 1.0f - (facm + fac * (1.0f - col[2])) * (1.0f - rgb[2]); + } + break; + case MA_RAMP_OVERLAY: + if (rgb[0] < 0.5f) { + rgb[0] *= facm + 2.0f * fac * col[0]; + } else { + rgb[0] = 1.0f - (facm + 2.0f * fac * (1.0f - col[0])) * (1.0f - rgb[0]); + } + if (rgb.length == 3) { + if (rgb[1] < 0.5f) { + rgb[1] *= facm + 2.0f * fac * col[1]; + } else { + rgb[1] = 1.0f - (facm + 2.0f * fac * (1.0f - col[1])) * (1.0f - rgb[1]); + } + if (rgb[2] < 0.5f) { + rgb[2] *= facm + 2.0f * fac * col[2]; + } else { + rgb[2] = 1.0f - (facm + 2.0f * fac * (1.0f - col[2])) * (1.0f - rgb[2]); + } + } + break; + case MA_RAMP_SUB: + rgb[0] -= fac * col[0]; + if (rgb.length == 3) { + rgb[1] -= fac * col[1]; + rgb[2] -= fac * col[2]; + } + break; + case MA_RAMP_DIV: + if (col[0] != 0.0) { + rgb[0] = facm * rgb[0] + fac * rgb[0] / col[0]; + } + if (rgb.length == 3) { + if (col[1] != 0.0) { + rgb[1] = facm * rgb[1] + fac * rgb[1] / col[1]; + } + if (col[2] != 0.0) { + rgb[2] = facm * rgb[2] + fac * rgb[2] / col[2]; + } + } + break; + case MA_RAMP_DIFF: + rgb[0] = facm * rgb[0] + fac * Math.abs(rgb[0] - col[0]); + if (rgb.length == 3) { + rgb[1] = facm * rgb[1] + fac * Math.abs(rgb[1] - col[1]); + rgb[2] = facm * rgb[2] + fac * Math.abs(rgb[2] - col[2]); + } + break; + case MA_RAMP_DARK: + tmp = fac * col[0]; + if (tmp < rgb[0]) { + rgb[0] = tmp; + } + if (rgb.length == 3) { + tmp = fac * col[1]; + if (tmp < rgb[1]) { + rgb[1] = tmp; + } + tmp = fac * col[2]; + if (tmp < rgb[2]) { + rgb[2] = tmp; + } + } + break; + case MA_RAMP_LIGHT: + tmp = fac * col[0]; + if (tmp > rgb[0]) { + rgb[0] = tmp; + } + if (rgb.length == 3) { + tmp = fac * col[1]; + if (tmp > rgb[1]) { + rgb[1] = tmp; + } + tmp = fac * col[2]; + if (tmp > rgb[2]) { + rgb[2] = tmp; + } + } + break; + case MA_RAMP_DODGE: + if (rgb[0] != 0.0) { + tmp = 1.0f - fac * col[0]; + if (tmp <= 0.0) { + rgb[0] = 1.0f; + } else if ((tmp = rgb[0] / tmp) > 1.0) { + rgb[0] = 1.0f; + } else { + rgb[0] = tmp; + } + } + if (rgb.length == 3) { + if (rgb[1] != 0.0) { + tmp = 1.0f - fac * col[1]; + if (tmp <= 0.0) { + rgb[1] = 1.0f; + } else if ((tmp = rgb[1] / tmp) > 1.0) { + rgb[1] = 1.0f; + } else { + rgb[1] = tmp; + } + } + if (rgb[2] != 0.0) { + tmp = 1.0f - fac * col[2]; + if (tmp <= 0.0) { + rgb[2] = 1.0f; + } else if ((tmp = rgb[2] / tmp) > 1.0) { + rgb[2] = 1.0f; + } else { + rgb[2] = tmp; + } + } + + } + break; + case MA_RAMP_BURN: + tmp = facm + fac * col[0]; + if (tmp <= 0.0) { + rgb[0] = 0.0f; + } else if ((tmp = 1.0f - (1.0f - rgb[0]) / tmp) < 0.0) { + rgb[0] = 0.0f; + } else if (tmp > 1.0) { + rgb[0] = 1.0f; + } else { + rgb[0] = tmp; + } + + if (rgb.length == 3) { + tmp = facm + fac * col[1]; + if (tmp <= 0.0) { + rgb[1] = 0.0f; + } else if ((tmp = 1.0f - (1.0f - rgb[1]) / tmp) < 0.0) { + rgb[1] = 0.0f; + } else if (tmp > 1.0) { + rgb[1] = 1.0f; + } else { + rgb[1] = tmp; + } + + tmp = facm + fac * col[2]; + if (tmp <= 0.0) { + rgb[2] = 0.0f; + } else if ((tmp = 1.0f - (1.0f - rgb[2]) / tmp) < 0.0) { + rgb[2] = 0.0f; + } else if (tmp > 1.0) { + rgb[2] = 1.0f; + } else { + rgb[2] = tmp; + } + } + break; + default: + throw new IllegalStateException("Unknown ramp type: " + type); + } + } + + /** + * This class returns a texture read from the file or from packed blender data. + * + * @param image + * image structure filled with data + * @param dataRepository + * the data repository + * @return the texture that can be used by JME engine + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + public Texture getTextureFromImage(Structure image, DataRepository dataRepository) throws BlenderFileException { + Texture result = (Texture) dataRepository.getLoadedFeature(image.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (result == null) { + Pointer pPackedFile = (Pointer) image.getFieldValue("packedfile"); + if (pPackedFile.isNull()) { + LOGGER.info("Reading texture from file!"); + String imagePath = image.getFieldValue("name").toString(); + result = this.loadTextureFromFile(imagePath, dataRepository); + } else { + LOGGER.info("Packed texture. Reading directly from the blend file!"); + Structure packedFile = pPackedFile.fetchData(dataRepository.getInputStream()).get(0); + Pointer pData = (Pointer) packedFile.getFieldValue("data"); + FileBlockHeader dataFileBlock = dataRepository.getFileBlock(pData.getOldMemoryAddress()); + dataRepository.getInputStream().setPosition(dataFileBlock.getBlockPosition()); + ImageLoader imageLoader = new ImageLoader(); + + // Should the texture be flipped? It works for sinbad .. + Image im = imageLoader.loadImage(dataRepository.getInputStream(), dataFileBlock.getBlockPosition(), true); + if (im != null) { + result = new Texture2D(im); + } + } + if (result != null) { + result.setName(String.valueOf(8));// 8 = TEX_IMAGE + result.setWrap(Texture.WrapMode.Repeat); + dataRepository.addLoadedFeatures(image.getOldMemoryAddress(), image.getName(), image, result); + } + } + return result; + } + + /** + * This method loads the textre from outside the blend file. + * + * @param name + * the path to the image + * @param dataRepository + * the data repository + * @return the loaded image or null if the image cannot be found + */ + protected Texture loadTextureFromFile(String name, DataRepository dataRepository) { + Image image = null; + ImageLoader imageLoader = new ImageLoader(); + FileInputStream fis = null; + ImageType[] imageTypes = ImageType.values(); + // TODO: would be nice to have the model asset key here to getthe models older in the assetmanager + + if (name.startsWith("//")) { + File modelFolder = new File(dataRepository.getBlenderKey().getName()); + File textureFolder = modelFolder.getParentFile(); + + if (textureFolder != null) { + name = textureFolder.getPath() + "/." + name.substring(1); // replace the // that means "relative" for blender (hopefully) + // with + } else { + name = name.substring(1); + } + + TextureKey texKey = new TextureKey(name, true); + Texture tex = dataRepository.getAssetManager().loadTexture(texKey); + image = tex.getImage(); + } + + // 2. Try using the direct path from the blender file + if (image == null) { + File textureFile = new File(name); + if (textureFile.exists() && textureFile.isFile()) { + LOGGER.log(Level.INFO, "Trying with: {0}", name); + try { + for (int i = 0; i < imageTypes.length && image == null; ++i) { + fis = new FileInputStream(textureFile); + image = imageLoader.loadImage(fis, imageTypes[i], false); + this.closeStream(fis); + } + } catch (FileNotFoundException e) { + assert false : e;// this should NEVER happen + } finally { + this.closeStream(fis); + } + } + } + + // 3. if 2 failed we start including the parent folder(s) to see if the texture + // can be found + if (image == null) { + String baseName = File.separatorChar != '/' ? name.replace(File.separatorChar, '/') : name; + int idx = baseName.lastIndexOf('/'); + while (idx != -1 && image == null) { + String texName = baseName.substring(idx + 1); + File textureFile = new File(texName); + if (textureFile.exists() && textureFile.isFile()) { + LOGGER.info("Trying with: " + texName); + try { + for (int i = 0; i < imageTypes.length && image == null; ++i) { + fis = new FileInputStream(textureFile); + image = imageLoader.loadImage(fis, imageTypes[i], false); + } + } catch (FileNotFoundException e) { + assert false : e;// this should NEVER happen + } finally { + this.closeStream(fis); + } + } + if (idx > 1) { + idx = baseName.lastIndexOf('/', idx - 1); + } else { + idx = -1; + } + } + } + + return image == null ? null : new Texture2D(image); + } + + /** + * This method closes the given stream. + * + * @param is + * the input stream that is to be closed + */ + protected void closeStream(InputStream is) { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + } + } + } + + /** + * An image loader class. It uses three loaders (AWTLoader, TGALoader and DDSLoader) in an attempt to load the image from the given + * input stream. + * + * @author Marcin Roguski (Kaelthas) + */ + protected static class ImageLoader extends AWTLoader { + private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName()); + + protected DDSLoader ddsLoader = new DDSLoader(); // DirectX image loader + + /** + * This method loads the image from the blender file itself. It tries each loader to load the image. + * + * @param inputStream + * blender input stream + * @param startPosition + * position in the stream where the image data starts + * @param flipY + * if the image should be flipped (does not work with DirectX image) + * @return loaded image or null if it could not be loaded + */ + public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) { + // loading using AWT loader + inputStream.setPosition(startPosition); + Image result = this.loadImage(inputStream, ImageType.AWT, flipY); + // loading using TGA loader + if (result == null) { + inputStream.setPosition(startPosition); + result = this.loadImage(inputStream, ImageType.TGA, flipY); + } + // loading using DDS loader + if (result == null) { + inputStream.setPosition(startPosition); + result = this.loadImage(inputStream, ImageType.DDS, flipY); + } + + if (result == null) { + LOGGER.warning("Image could not be loaded by none of available loaders!"); + } + + return result; + } + + /** + * This method loads an image of a specified type from the given input stream. + * + * @param inputStream + * the input stream we read the image from + * @param imageType + * the type of the image {@link ImageType} + * @param flipY + * if the image should be flipped (does not work with DirectX image) + * @return loaded image or null if it could not be loaded + */ + public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) { + Image result = null; + switch (imageType) { + case AWT: + try { + result = this.load(inputStream, flipY); + } catch (Exception e) { + LOGGER.info("Unable to load image using AWT loader!"); + } + break; + case DDS: + try { + result = ddsLoader.load(inputStream); + } catch (Exception e) { + LOGGER.info("Unable to load image using DDS loader!"); + } + break; + case TGA: + try { + result = TGALoader.load(inputStream, flipY); + } catch (Exception e) { + LOGGER.info("Unable to load image using TGA loader!"); + } + break; + default: + throw new IllegalStateException("Unknown image type: " + imageType); + } + return result; + } + } + + /** + * Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files + * + * @author Marcin Roguski (Kaelthas) + */ + public static enum ImageType { + AWT, TGA, DDS; + } + + /** + * The result pixel of generated texture computations; + * + * @author Marcin Roguski (Kaelthas) + */ + protected static class TexResult implements Cloneable { + public float tin, tr, tg, tb, ta; + public int talpha; + public float[] nor; + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + /** + * A class constaining the colorband data. + * + * @author Marcin Roguski (Kaelthas) + */ + protected static class ColorBand { + public int flag, tot, cur, ipotype; + public CBData[] data = new CBData[32]; + + /** + * Constructor. Loads the data from the given structure. + * + * @param cbdataStructure + * the colorband structure + */ + @SuppressWarnings("unchecked") + public ColorBand(Structure colorbandStructure) { + this.flag = ((Number) colorbandStructure.getFieldValue("flag")).intValue(); + this.tot = ((Number) colorbandStructure.getFieldValue("tot")).intValue(); + this.cur = ((Number) colorbandStructure.getFieldValue("cur")).intValue(); + this.ipotype = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue(); + DynamicArray data = (DynamicArray) colorbandStructure.getFieldValue("data"); + for (int i = 0; i < data.getTotalSize(); ++i) { + this.data[i] = new CBData(data.get(i)); + } + } + } + + /** + * Class to store the single colorband unit data. + * + * @author Marcin Roguski (Kaelthas) + */ + protected static class CBData implements Cloneable { + public float r, g, b, a, pos; + public int cur; + + /** + * Constructor. Loads the data from the given structure. + * + * @param cbdataStructure + * the structure containing the CBData object + */ + public CBData(Structure cbdataStructure) { + this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue(); + this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue(); + this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue(); + this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue(); + this.pos = ((Number) cbdataStructure.getFieldValue("pos")).floatValue(); + this.cur = ((Number) cbdataStructure.getFieldValue("cur")).intValue(); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + public static class GeneratedTextureData { + public ByteBuffer luminanceData; + public ByteBuffer rgbData; + public Format rgbFormat; + public int width; + public int height; + + public GeneratedTextureData(ByteBuffer luminanceData, ByteBuffer rgbData, Format rgbFormat, int width, int height) { + this.luminanceData = luminanceData; + this.rgbData = rgbData; + this.rgbFormat = rgbFormat; + this.width = width; + this.height = height; + } + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/noiseconstants.dat b/engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/noiseconstants.dat new file mode 100644 index 0000000000000000000000000000000000000000..81fea0b612c38d58d0fefa63088af7caf70e2423 GIT binary patch literal 19598 zcmeI&`CCov|M>B{y=(6s&2@w#AyO!c&|a@s=AlrTlMJDdIYLsVW9E39Ib=9y4#$*f zuiL>f9P>P$Q|5%DRBG>byYKRLKG*&FUf;jqdwueY>v~*Q)4kTZ*V@n5I`E0vOqs+~ z13OjydG?~ZId<(-j7^)sn3|#Oq*40)eLf?LI|_ql7oc_Qbc|kB7gK7#gx>@FvB`xQ z2pcn`D-D~$pPzd`THX0*GhjB>($2>!S9Uq+Oh$B@_eMh?lRoH)eGwE-GuQ{8rGQ5 zR9f%&9GVWQ4hbPo;c9p)Z0y!m8iRSTcJdH-d1yM+*jNXyH`oZ0+6hN@TZ-Nb{c(EC zAu0a{J%;ZN!uIhiq3-o2SnHe>WF*zaM3(~CF7?DF9)G}un2G53aSL|4)ee@{-Xs+z zuY>U13LF?^z}}vt1_`Y2m5*?C7bmvf+IxH~ckR%2MITiJ!4vy9&tJ zw+BP~Vx;C@he6Mb=kWW{+wgWJgMw8h;67kKCi*QzP2fW8QZ!GB&VGc$k3PWaH$7ol z9UWYmFctgj)1-UOJ7F$Afh`8up_QE{MDz&2MpaF4Zs}0TD{KeqRCi!d_qpJ}beK3a z8GAfFg1%4VaC8qD-h66=b(Tzp;wO5zQ02aUW9$y-|GGIkKl&u?S#%x)=X6DvV~$u| z>jQ&2xB_=LBK?'C;5{gLRKaQ;L$oOCi8exKYMb{tv@+Y+O2y6rjacDDt*uJQ%@ zC%A*W!cV%GdlCa@K7l9B8fkddX;Q)B1+d3I9zCk*(fiC&ke4|_%+p}WCVxDy0)3IF2oU4M*2wo>;&6M~vCztN+Iw0uNnwqT8#^dfOk%(Oq4L zjbig~#&Km`{f|hmmeqzA^Os5`GbW(Tg5&5@G8a}V$0aK91bCKMVf2#UU?a9c{**TE4-AD@6Dr%u5dmu6x7qsHj=Za+Lf*9wETHb(o}C*i^3XK?dmAU5xJ71lhQ ziKBWR#K4>muv3ncs%3VCdFyAx(xj;{az-!oRzHPfRSrq>C$5lwSrh@E!3S(EcZNUK z&c`wR+raFidzjL9ihlWXJzTYJ3dQx?;Ed6Iq}ACrm^!>bx^i$7hIZs|W85g|dH20= zq}&;bd!}Mg%y9U+#~HgSKR>v*id6i07dqL^!_r~{+G5L$@sde@d(R)k{0=KIpI z-kZ=%y*za1&>rw^-9O;-Aq&dwE})Gm2<F*!)H&yj$}Xox%=b)bmGBW*!l`sHh71 z9B6>{O;X_0(^P2LqBcgZ%)~YklW|a4DW-S)Rr)KfwRC7@EeyXBihk_|!l~7nuyev3 zDad)J)MVBo*cQGS_9X4axX(wi>6^~*dDV1O->!~5W7?tj)gcgkOABX~N8o_-5oncU zkN9#GhP504Hx5pLuS35;&J;UTwY!aRK?AVie=?x|YM8c1>XP z?ThH$IR<)VmP79@OR>de1N>Te9~SI5t^bhm3BBEZgZ$I;uyvjrFgyx-oZ4b|&qpX- znF6MJ^Pp{k23ybhB+2!Xqxz|`s(?O_WqCLO>e=f)U3 zq&|+0aYMcFCk(w<6FN;Yc;L|<{c2r-F@$&=V`R<1(?Vbgl0!QNDa3ebTY=Ncg zJ>iIJI{Gc13vVOCu)96SYA0?>eS`a8OVet2G;lV$u)ETjQ%j*=p$2X*3BpE)6EW$R z2plnJHg=jk1OvN_(ceAx1U9bjhut=0A#0e7z0L2icMU(RKcoeeTMt3aRu_1^s0d#4 z%*XZvC!qJ>`Y<9Y687b0VnV@XsPHa<6zA3Ov~U}Cyty2@JygNStG^?wXb#zn-su-K z>yF*eeS~)7hr*gF2~e>l8$4PRN!y-TVS;k+X)$aWEY)X9i(QPcJ9MgU+tSioyE^YV6n1{j8 zsP8OvUa%VC93}nkH50JUYkRak`w7!OyJPLkbz%B@os=@X0Co;-3~P<2G0k@-{NYv; z^}SwU$BLn7Cwsw~qHWly)(v#ecSSF&br3W?3!7%fV0io+jBD2jz3;EV9yiiZHSG?3 zy%FB#eUH~L?|>gKVZ^F<=*V4fjLiIaQaWn z^$S}sM*9?RbdA4&o*(OAtwS%d`>7MyG3!?;>v|jPdv+Mso!DQ>&FO$`XSG1r=h^6& zvjVj{f?(I#T#V{r3)@}K!~T^$QGeks%-=K^^@s0B-SU!AA2Ax!8h(WhK4-vPiiHQi zEWrtn%TW^$g9CT`3BlXTVa5q9lparzbenx)#e5@%HF3wbGmm1@`ZvKb{V>k6%*zq(96Yj>tgt>h&?dlvU zZT(CP-Rgi{gAd~13z5>YVXe{kXAju2zrX(XqQ=;5S30&ocMltNod-L%4#VE@1=8mm ziI@;n7gI-H0-re>pi`@>=)cV$2d2FbojSS?x~!fK@w3KDc^k6eQH|rMy|Ntg%R^DK zP`M6ve~=nq9qKu&M%EjrK?AaS(W|T5xmNa~w7KDvmhf3Ds75W7uUK zTGu{+5`KZ8kPlL5N*mMFTQ2hn}rH5^l38>@_+0o4y4fXBOsVh`o# zTq{WNyiQn^mzyK>m#tem?J7Hr*kGrTM7f}?AeVz8t@XA zOvFx%J^WIN>YE8K2E2uxsqtt%A{w2%PD&rWt|I6e&glOiIPhc`yx6`IZ3E(P%4QWD z{YS#!C@X!l!HYoCvk$tSnFmM8YGS9s(eTvjv$Xo9A2Q|)3;!O6iP_blm)8v#5&Hxu z*qLDGx}#G5FN2`Wb{Blfd4+*nJ)rSPcWmW<0@_aP2YZ{Zh6Q*2g4b!=A@u|WB6JKM@?uuxXY zKCpf4A6CdbSpxHAwb&=d*xzgoYs#K8WNp|S)`E3sN$fOh$!@c+%#KZG&Dk9`fE6(v zTf*YmA{M|-vSVx=o6Eki6|6eD%%YeZ8^IQ^F02k~$M&!ccASMVS9XNeWwTg6c9x~H zF>EvYkwvg8>=~QM9xxx)gT=5uESW{J8|)~X!OpM?>>4}4Ub5HhBP(a?Syi@{-D6u= zEZfG0vjR4o4P`IbMOL3pVqMu%_7e+d^H?xz#rCsl>`&&!tk@&AiaE32S%0>dHDgBB zfsJLH1+kymU}no!vmImCb-B`wQiq03+{q*(_&W(P8z+Dh%0W z!)JETf?GhYbRzo_uW4_Q4;(7tt)oiix3P9uy}1?otye>zNGrqhiVryLi4k2po1~PC zV2r#`25W=z`H-e6`C_jkSesyke^N@Y&%rX@e^r@0PWhdRlk5$j0$zj5G%Zh69@?YU zz2TFJH1d?rIXG!xnY^IB6X>|%j>a3ZlKdja~*HgVoXM=L%R&I*HO;ud;Q7aeT*I@Ev6)eOO zsko;M#rG^w5@N>CX=)j+*eN&6aygPQj+-pp*WY9~gXs*%Zrza=czou~vnYN}xvz&+D)}oW6@-=a4u76;X-ZMo1Wy+0qY$=Qjw89Y`P4HajWXQV> zhL49z(yVuMx; zwxycn;8wPLMx0uDEO}@Gu$=XU@t2(It)|EeI7et_dGRr z?rcKG92;1ntVgX!7VM&||C#Zxpi6~WimFosXB=RL(y;2#+v@|LHb)~j`$;EXRE|fY zYdQD~w(!`j(eJoVl)^&Qo)2WGg2R@V?@W%7ZNS-l#fzEo=fhBYX!?ol&k@kF2Cyjhh z`vn7EGhSU?A>W=`4!e9+7_-RC0}t!q%;ZAub&1K(B5kpMO&fWqx=bE0_#b(Fzd}Av z`PwD7zM{2P8TPzm!Ld8dAa}Neep21Qu8lQvajcVk#m#{SCo^f{;6)hxMFY(eK0@R) zC!BhK!Q;kNctVECke;0`pSIS?Z615zsF&6-$FZ2($6I(fmdUF=Du0)@g1i1;l*VPh zgEfK5`98K3C$%V*H~s7=y~-{!Y;|f2uly~1_+lGqlkygaKQO_a{e`@#uS$-YV2eR% zCQ11jI3Q5NH9uMNHY>|`{YV=~cKpPL&QZbBl42#MxpG~USsobh5u>)1Cgo{?-}@>J-x`O}%Gx+xw2`*6P?$H*LApNp9G~(TnH29(s#SwrajzGjg%7mN#l;jjk`u*h<-Vn&jB?#_1MW+rI`+@v4B42}Yh8 ztCig{yd>l8I@o@q8Qn%1VdY^pAHG{zvw&;xa!C;n&oXgMbsN~UBp>w+)YxRT?o46% zDyXgOOEXg~hLLrAV4jB=U3%E?&WRe{bb193I%AZ3Il1zwX%@M~cr!$%f07I8Yq?ta z-9w@omwvQDm-ZGo9$vs5hiiDR<~D{6KlyQ$wGPAXnBXDPadurTd4_3mkh%=3yJ)cS z*i!jL!>@+obkmvRssnsheYJEotc4ttTLY)vDC4y@Sg`&RBjhf0z!AzCzPRrwsn4Zz zhabu@thtq()vpAu`g{_88WN$z2bfZbw^l3X-1{PL z4JnnkW;kO125R^_StqA3YbnO-4o-8lNONvI!RYfg=rlwPh0kp;_P9k3++&Nii_7Kp zJB-+RwQ|me6mfiD;hx8|-1SL0{PCd-TRB?f+jfpz|DghH-<3&i@eVqSw!+>+)V%uw zBUXzyNw)&Mc#9_GJouDF|2Tai@WKKNz%s0(Tn7bb3ZPG-7a!Q(#DmOM-0k;r`ObYu z`N7W`u1~k1Za|s*Celjonf0E>(Nil5_rsr8z;^aFYeY4(qUe8*6T_T+;_074WFL zTKTqez20=v@HV~7+)jBuxzkv~2fA6w#TOZL9B;;ElgkX-hcv;Ep%zFOUxW?DS@G7# zO)xXlh{?(tG|PIyrzKg*$t52R*4a0q>Ta`qRS7zY%GyN+7}4Ri4tyi4!7Y!^Xt&&& zhbrga`9sQabTwjVjZ*#bj0_xi*$gKGjC!q=Gp5(p8TR~ShskeshWWStkymz5bBM9l zYtpNte?tq8b`IDf!G0M z|Gkf2pXmr)l<(*G6T@CX<%VVFT;SU^G9aZ4o*yca{ut5@$1f{|gPFyK=*%3rqudKd z>8)XJKrv2?(;Av=b(h~M&of<>N|i63s=RPy5Mc4wU`*1^L1RnKfyV$Re{*4QA+ zg7&9O+*;YcR_rL}{giur-Yt_Hq&&Z*9yLi3theOExA7UzjL_wr4Np~`y?n!}8}>|a zhVlJM__VjWGrur1$8aO}Ot--N{tSIOn$Vpa;cH5XRN(lSPgc&|)%R6g_f^BYoz}=} z4?FQX%6>CYxmIw$+0Y>)o=1jhx!Vx+x4j3727ckOql_rIsO0JkJXorL zzaBgAwjI>y^U7?v$#ha)eru`a;3s^RofcEVjWC0k8}?>2{_iINB?zcgm7fX!cYh|V zJpPr(U&)2)fdf0G>|0iCl%>Yh|LKLQ|LFxxx%Y}*Ae5E^p|l|gr8Pk)?FvF^VGv4N zgHT!>h?4#wl$Hpgv`Gl1bwViZ6jVjA5K7yHP+BpB(w-rdmJOk_aR{ZgLn!SYLTLdJ zN?Qm~QbmN)K0=g~5}~x22&MHzDD5ahX;BeM+lo+HS%lKwB9xXFp|rsWr8R~q=`ut~ zp%F@3jZj)`gwlRPl$0EywCM0gwp;cl$J1}q=^ZobxbJjWJF0ZBTCwtP+HN5lAb1%mNlWYu@NP; zO(^YdLTP~$N?V*zTIGb&J|~oxI-#`L38nQ;DD8McY0)D}+8$9-`GnHmN0gL5qND)| zr8Q6}?SevSArwkmAyHBdh0=aVl$1oFv?&Uubx|noj6!K~6iVA8QBom^k{&6PmPw(s zQ4%G!QYh_~LTSMiN?WE-S~Z2zzA2QJPNB4U5+(IhDD9v^X%Qt#+Ne-kNr{qPDwLK} zp|qhAB{h{O>8eCYVHHYSt58~9h0^{il$Kbbw8;vkbyg_tv_fgIB}&?@P+D<`lAcSH zlwF~;@d~B2mniAJLTLdON?Wi{T7`wuJ}i`$VxpuO3#Ii~DDB8XX;CIh+OkkunT68c zER>dKp|n8@r8Sx;>C!@Jp%zM8wNP5Eh0=a4l$LCvv}p^abz3Oy+(b$77E0SUQBuK$ z(jG39mT{t_kqf1@Tqy14LTN!4N?STnQq_ghzAluOcA>Pn6D9SXDCzJ*X^|I7+dNTH z>4nl>FO-&hp|s%(r8QqD?fOD#;U`L3zffBJh0^{{luQDMl9_-|Ivo&7=LACO#6T#W z9SEgU1fg`EAe2rPgwh!UQ8H~1O6Lwj=>$S3oka+xQwgDTJ|UD&DumLRg-|-Z5K8A5 zLg_?9D4lHxrBe=}blxG9PCkUv8Hi9i4G~J`B0}keL@1q=2&GdKp>%#ClulBF(wT}- zI$a@3<}5<##6>8by$GdK7@>3?Ba}{Ngwh#}P&%y$h8o#hCnQyrpYz9W=Q zdW6!Mk5D@OAxh>zLg_?ED4h)n{ePxJ|9xSsvj2Wt2Bm|O_teCi(KptFR`%b2*NnU~ zCswDj|9)Sc%KrO(9V+|p_jRc3zyDqrdk- zQ^nO%*?<3Coyz|EZ6VZ^{rCI%P}zUKuR~@3{k{&B{rBJVfxL4q?jrQvPs_eht*P*iieqV?GpZo9s0mdBt!2kdN literal 0 HcmV?d00001 diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/structures/AbstractInfluenceFunction.java b/engine/src/blender/com/jme3/scene/plugins/blender/structures/AbstractInfluenceFunction.java new file mode 100644 index 000000000..5216cf354 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/structures/AbstractInfluenceFunction.java @@ -0,0 +1,225 @@ +package com.jme3.scene.plugins.blender.structures; + +import java.util.logging.Logger; + +import com.jme3.animation.Bone; +import com.jme3.animation.BoneAnimation; +import com.jme3.animation.BoneTrack; +import com.jme3.animation.Skeleton; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.structures.Constraint.Space; +import com.jme3.scene.plugins.blender.utils.DataRepository; +import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.utils.Pointer; + +/** + * This class is used to calculate the constraint. The following methods should be implemented: affectLocation, + * affectRotation and affectScale. This class also defines all constants required by known deriving classes. + * @author Marcin Roguski + */ +public abstract class AbstractInfluenceFunction { + protected static final Logger LOGGER = Logger.getLogger(AbstractInfluenceFunction.class.getName()); + + protected static final float IK_SOLVER_ERROR = 0.5f; + + //DISTLIMIT + protected static final int LIMITDIST_INSIDE = 0; + protected static final int LIMITDIST_OUTSIDE = 1; + protected static final int LIMITDIST_ONSURFACE = 2; + + //CONSTRAINT_TYPE_LOCLIKE + protected static final int LOCLIKE_X = 0x01; + protected static final int LOCLIKE_Y = 0x02; + protected static final int LOCLIKE_Z = 0x04; + + //ROTLIKE + protected static final int ROTLIKE_X = 0x01; + protected static final int ROTLIKE_Y = 0x02; + protected static final int ROTLIKE_Z = 0x04; + protected static final int ROTLIKE_X_INVERT = 0x10; + protected static final int ROTLIKE_Y_INVERT = 0x20; + protected static final int ROTLIKE_Z_INVERT = 0x40; + protected static final int ROTLIKE_OFFSET = 0x80; + + //SIZELIKE + protected static final int SIZELIKE_X = 0x01; + protected static final int SIZELIKE_Y = 0x02; + protected static final int SIZELIKE_Z = 0x04; + protected static final int SIZELIKE_OFFSET = 0x80; + + /* LOCLIKE_TIP is a depreceated option... use headtail=1.0f instead */ + //protected static final int LOCLIKE_TIP = 0x08; + protected static final int LOCLIKE_X_INVERT = 0x10; + protected static final int LOCLIKE_Y_INVERT = 0x20; + protected static final int LOCLIKE_Z_INVERT = 0x40; + protected static final int LOCLIKE_OFFSET = 0x80; + + //LOCLIMIT, SIZELIMIT + protected static final int LIMIT_XMIN = 0x01; + protected static final int LIMIT_XMAX = 0x02; + protected static final int LIMIT_YMIN = 0x04; + protected static final int LIMIT_YMAX = 0x08; + protected static final int LIMIT_ZMIN = 0x10; + protected static final int LIMIT_ZMAX = 0x20; + + //ROTLIMIT + protected static final int LIMIT_XROT = 0x01; + protected static final int LIMIT_YROT = 0x02; + protected static final int LIMIT_ZROT = 0x04; + + /** The type of the constraint. */ + protected ConstraintType constraintType; + /** The data repository. */ + protected DataRepository dataRepository; + + /** + * Constructor. + * @param constraintType + * the type of the current constraint + * @param dataRepository + * the data repository + */ + public AbstractInfluenceFunction(ConstraintType constraintType, DataRepository dataRepository) { + this.constraintType = constraintType; + this.dataRepository = dataRepository; + } + + /** + * This method validates the constraint type. It throws an IllegalArgumentException if the constraint type of the + * given structure is invalid. + * @param constraintStructure + * the structure with constraint data + */ + protected void validateConstraintType(Structure constraintStructure) { + if(!constraintType.getClassName().equalsIgnoreCase(constraintStructure.getType())) { + throw new IllegalArgumentException("Invalud structure type (" + constraintStructure.getType() + ") for the constraint: " + constraintType.getClassName() + '!'); + } + } + + /** + * This method affects the bone animation tracks for the given skeleton. + * @param skeleton + * the skeleton containing the affected bones by constraint + * @param boneAnimation + * the bone animation baked traces + * @param constraint + * the constraint + */ + public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) {} + + /** + * This method returns the bone traces for the bone that is affected by the given constraint. + * @param skeleton + * the skeleton containing bones + * @param boneAnimation + * the bone animation that affects the skeleton + * @param constraint + * the affecting constraint + * @return the bone track for the bone that is being affected by the constraint + */ + protected BoneTrack getBoneTrack(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { + Long boneOMA = constraint.getBoneOMA(); + Bone bone = (Bone)dataRepository.getLoadedFeature(boneOMA, LoadedFeatureDataType.LOADED_FEATURE); + int boneIndex = skeleton.getBoneIndex(bone); + if(boneIndex != -1) { + //searching for track for this bone + for(BoneTrack boneTrack : boneAnimation.getTracks()) { + if(boneTrack.getTargetBoneIndex() == boneIndex) { + return boneTrack; + } + } + } + return null; + } + + /** + * This method returns the target or subtarget object (if specified). + * @param constraint + * the constraint instance + * @return target or subtarget feature + */ + protected Object getTarget(Constraint constraint, LoadedFeatureDataType loadedFeatureDataType) { + Long targetOMA = ((Pointer)constraint.getData().getFieldValue("tar")).getOldMemoryAddress(); + Object targetObject = dataRepository.getLoadedFeature(targetOMA, loadedFeatureDataType); + String subtargetName = constraint.getData().getFieldValue("subtarget").toString(); + if(subtargetName.length() > 0) { + return dataRepository.getLoadedFeature(subtargetName, loadedFeatureDataType); + } + return targetObject; + } + + /** + * This method returns target's object location. + * @param constraint + * the constraint instance + * @return target's object location + */ + protected Vector3f getTargetLocation(Constraint constraint) { + Long targetOMA = ((Pointer)constraint.getData().getFieldValue("tar")).getOldMemoryAddress(); + Space targetSpace = constraint.getTargetSpace(); + Node targetObject = (Node)dataRepository.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); + switch(targetSpace) { + case CONSTRAINT_SPACE_LOCAL: + return targetObject.getLocalTranslation(); + case CONSTRAINT_SPACE_WORLD: + return targetObject.getWorldTranslation(); + default: + throw new IllegalStateException("Invalid space type for target object: " + targetSpace.toString()); + } + } + + /** + * This method returns target's object location in the specified frame. + * @param constraint + * the constraint instance + * @param frame + * the frame number + * @return target's object location + */ + protected Vector3f getTargetLocation(Constraint constraint, int frame) { + return this.getTargetLocation(constraint);//TODO: implement getting location in a specified frame + } + + /** + * This method returns target's object rotation. + * @param constraint + * the constraint instance + * @return target's object rotation + */ + protected Quaternion getTargetRotation(Constraint constraint) { + Long targetOMA = ((Pointer)constraint.getData().getFieldValue("tar")).getOldMemoryAddress(); + Space targetSpace = constraint.getTargetSpace(); + Node targetObject = (Node)dataRepository.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); + switch(targetSpace) { + case CONSTRAINT_SPACE_LOCAL: + return targetObject.getLocalRotation(); + case CONSTRAINT_SPACE_WORLD: + return targetObject.getWorldRotation(); + default: + throw new IllegalStateException("Invalid space type for target object: " + targetSpace.toString()); + } + } + + /** + * This method returns target's object scale. + * @param constraint + * the constraint instance + * @return target's object scale + */ + protected Vector3f getTargetScale(Constraint constraint) { + Long targetOMA = ((Pointer)constraint.getData().getFieldValue("tar")).getOldMemoryAddress(); + Space targetSpace = constraint.getTargetSpace(); + Node targetObject = (Node)dataRepository.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); + switch(targetSpace) { + case CONSTRAINT_SPACE_LOCAL: + return targetObject.getLocalScale(); + case CONSTRAINT_SPACE_WORLD: + return targetObject.getWorldScale(); + default: + throw new IllegalStateException("Invalid space type for target object: " + targetSpace.toString()); + } + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/structures/BezierCurve.java b/engine/src/blender/com/jme3/scene/plugins/blender/structures/BezierCurve.java new file mode 100644 index 000000000..bf7b43c77 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/structures/BezierCurve.java @@ -0,0 +1,137 @@ +package com.jme3.scene.plugins.blender.structures; + +import java.util.ArrayList; +import java.util.List; + +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.utils.DynamicArray; + +/** + * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize + * floating point operations errors. + * @author Marcin Roguski + */ +public class BezierCurve { + public static final int X_VALUE = 0; + public static final int Y_VALUE = 1; + public static final int Z_VALUE = 2; + + /** + * The type of the curve. Describes the data it modifies. + * Used in ipos calculations. + */ + private int type; + /** The dimension of the curve. */ + private int dimension; + /** A table of the bezier points. */ + private float[][][] bezierPoints; + + @SuppressWarnings("unchecked") + public BezierCurve(final int type, final List bezTriples, final int dimension) { + if(dimension != 2 && dimension != 3) { + throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!"); + } + this.type = type; + this.dimension = dimension; + //first index of the bezierPoints table has the length of triples amount + //the second index points to a table od three points of a bezier triple (handle, point, handle) + //the third index specifies the coordinates of the specific point in a bezier triple + bezierPoints = new float[bezTriples.size()][3][dimension]; + int i = 0, j, k; + for(Structure bezTriple : bezTriples) { + DynamicArray vec = (DynamicArray)bezTriple.getFieldValue("vec"); + for(j = 0; j < 3; ++j) { + for(k = 0; k < dimension; ++k) { + bezierPoints[i][j][k] = vec.get(j, k).floatValue(); + } + } + ++i; + } + } + + /** + * This method evaluates the data for the specified frame. The Y value is returned. + * @param frame + * the frame for which the value is being calculated + * @param valuePart + * this param specifies wheather we should return the X, Y or Z part of the result value; it should have + * one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result + * Z_VALUE - the Z factor of the result + * @return the value of the curve + */ + public float evaluate(int frame, int valuePart) { + for(int i = 0; i < bezierPoints.length - 1; ++i) { + if(frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) { + float t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]); + float oneMinusT = 1.0f - t; + float oneMinusT2 = oneMinusT * oneMinusT; + float t2 = t * t; + return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t; + } + } + if(frame < bezierPoints[0][1][0]) { + return bezierPoints[0][1][1]; + } else { //frame>bezierPoints[bezierPoints.length-1][1][0] + return bezierPoints[bezierPoints.length - 1][1][1]; + } + } + + /** + * This method returns the frame where last bezier triple center point of the bezier curve is located. + * @return the frame number of the last defined bezier triple point for the curve + */ + public int getLastFrame() { + return (int)bezierPoints[bezierPoints.length - 1][1][0]; + } + + /** + * This method returns the type of the bezier curve. The type describes the parameter that this curve modifies + * (ie. LocationX or rotationW of the feature). + * @return the type of the bezier curve + */ + public int getType() { + return type; + } + + /** + * This method returns a list of control points for this curve. + * @return a list of control points for this curve. + */ + public List getControlPoints() { + List controlPoints = new ArrayList(bezierPoints.length * 3); + for(int i = 0;i loc/rot/size) constraint */ + CONSTRAINT_TYPE_TRANSFORM(19, "bTransformConstraint"), + /* shrinkwrap (loc/rot) constraint */ + CONSTRAINT_TYPE_SHRINKWRAP(20, "bShrinkwrapConstraint"); + + /** The constraint's id (in blender known as 'type'). */ + private int constraintId; + /** The name of constraint class used by blender. */ + private String className; + /** The map containing class names and types of constraints. */ + private static Map typesMap = new HashMap(ConstraintType.values().length); + /** The map containing class names and types of constraints. */ + private static Map idsMap = new HashMap(ConstraintType.values().length); + static { + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_NULL.constraintId), CONSTRAINT_TYPE_NULL); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_CHILDOF.constraintId), CONSTRAINT_TYPE_CHILDOF); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_KINEMATIC.constraintId), CONSTRAINT_TYPE_KINEMATIC); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_FOLLOWPATH.constraintId), CONSTRAINT_TYPE_FOLLOWPATH); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ROTLIMIT.constraintId), CONSTRAINT_TYPE_ROTLIMIT); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCLIMIT.constraintId), CONSTRAINT_TYPE_LOCLIMIT); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SIZELIMIT.constraintId), CONSTRAINT_TYPE_SIZELIMIT); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ROTLIKE.constraintId), CONSTRAINT_TYPE_ROTLIKE); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCLIKE.constraintId), CONSTRAINT_TYPE_LOCLIKE); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SIZELIKE.constraintId), CONSTRAINT_TYPE_SIZELIKE); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_PYTHON.constraintId), CONSTRAINT_TYPE_PYTHON); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ACTION.constraintId), CONSTRAINT_TYPE_ACTION); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCKTRACK.constraintId), CONSTRAINT_TYPE_LOCKTRACK); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_DISTLIMIT.constraintId), CONSTRAINT_TYPE_DISTLIMIT); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_STRETCHTO.constraintId), CONSTRAINT_TYPE_STRETCHTO); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_MINMAX.constraintId), CONSTRAINT_TYPE_MINMAX); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_RIGIDBODYJOINT.constraintId), CONSTRAINT_TYPE_RIGIDBODYJOINT); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_CLAMPTO.constraintId), CONSTRAINT_TYPE_CLAMPTO); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_TRANSFORM.constraintId), CONSTRAINT_TYPE_TRANSFORM); + idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SHRINKWRAP.constraintId), CONSTRAINT_TYPE_SHRINKWRAP); + } + /** + * Constructor. Stores constraint type and class name. + * @param constraintId + * the constraint's type + * @param className + * the constraint's type name + */ + private ConstraintType(int constraintId, String className) { + this.constraintId = constraintId; + this.className = className; + } + + /** + * This method returns the type by given constraint id. + * @param constraintId + * the id of the constraint + * @return the constraint type enum value + */ + public static ConstraintType valueOf(int constraintId) { + return idsMap.get(Integer.valueOf(constraintId)); + } + + /** + * This method returns the constraint's id (type). + * @return the constraint's id (type) + */ + public int getConstraintId() { + return constraintId; + } + + /** + * This method returns the constraint's class name. + * @return the constraint's class name + */ + public String getClassName() { + return className; + } + + /** + * This method returns constraint enum type by the given class name. + * @param className + * the blender's constraint class name + * @return the constraint enum type of the specified class name + */ + public static ConstraintType getByBlenderClassName(String className) { + ConstraintType result = typesMap.get(className); + if(result == null) { + ConstraintType[] constraints = ConstraintType.values(); + for(ConstraintType constraint : constraints) { + if(constraint.className.equals(className)) { + return constraint; + } + } + } + return result; + } + + /** + * This method returns the type value of the last defined constraint. It can be used for allocating tables for + * storing constraint procedures since not all type values from 0 to the last value are used. + * @return the type value of the last defined constraint + */ + public static int getLastDefinedTypeValue() { + return CONSTRAINT_TYPE_SHRINKWRAP.getConstraintId(); + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/structures/Ipo.java b/engine/src/blender/com/jme3/scene/plugins/blender/structures/Ipo.java new file mode 100644 index 000000000..d75e5d79a --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/structures/Ipo.java @@ -0,0 +1,176 @@ +package com.jme3.scene.plugins.blender.structures; + +import com.jme3.animation.BoneTrack; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; + +/** + * This class is used to calculate bezier curves value for the given frames. The Ipo (interpolation object) consists + * of several b-spline curves (connected 3rd degree bezier curves) of a different type. + * @author Marcin Roguski + */ +public class Ipo { + public static final int AC_LOC_X = 1; + public static final int AC_LOC_Y = 2; + public static final int AC_LOC_Z = 3; + public static final int OB_ROT_X = 7; + public static final int OB_ROT_Y = 8; + public static final int OB_ROT_Z = 9; + public static final int AC_SIZE_X = 13; + public static final int AC_SIZE_Y = 14; + public static final int AC_SIZE_Z = 15; + public static final int AC_QUAT_W = 25; + public static final int AC_QUAT_X = 26; + public static final int AC_QUAT_Y = 27; + public static final int AC_QUAT_Z = 28; + + /** A list of bezier curves for this interpolation object. */ + private BezierCurve[] bezierCurves; + /** Each ipo contains one bone track. */ + private BoneTrack calculatedTrack; + + /** + * Constructor. Stores the bezier curves. + * @param bezierCurves + * a table of bezier curves + */ + public Ipo(BezierCurve[] bezierCurves) { + this.bezierCurves = bezierCurves; + } + + /** + * This method calculates the ipo value for the first curve. + * @param frame + * the frame for which the value is calculated + * @return calculated ipo value + */ + public float calculateValue(int frame) { + return this.calculateValue(frame, 0); + } + + /** + * This method calculates the ipo value for the curve of the specified index. Make sure you do not exceed the + * curves amount. Alway chech the amount of curves before calling this method. + * @param frame + * the frame for which the value is calculated + * @param curveIndex + * the index of the curve + * @return calculated ipo value + */ + public float calculateValue(int frame, int curveIndex) { + return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE); + } + + /** + * This method returns the curves amount. + * @return the curves amount + */ + public int getCurvesAmount() { + return bezierCurves.length; + } + + /** + * This method returns the frame where last bezier triple center point of the specified bezier curve is located. + * @return the frame number of the last defined bezier triple point for the specified ipo + */ + public int getLastFrame() { + int result = 1; + for(int i = 0; i < bezierCurves.length; ++i) { + int tempResult = bezierCurves[i].getLastFrame(); + if(tempResult > result) { + result = tempResult; + } + } + return result; + } + + public void modifyTranslation(int frame, Vector3f translation) { + if(calculatedTrack!=null) { + calculatedTrack.getTranslations()[frame].set(translation); + } + } + + public void modifyRotation(int frame, Quaternion rotation) { + if(calculatedTrack!=null) { + calculatedTrack.getRotations()[frame].set(rotation); + } + } + + public void modifyScale(int frame, Vector3f scale) { + if(calculatedTrack!=null) { + calculatedTrack.getScales()[frame].set(scale); + } + } + + /** + * This method calculates the value of the curves as a bone track between the specified frames. + * @param boneIndex + * the index of the bone for which the method calculates the tracks + * @param startFrame + * the firs frame of tracks (inclusive) + * @param stopFrame + * the last frame of the tracks (inclusive) + * @param fps + * frame rate (frames per second) + * @return bone track for the specified bone + */ + public BoneTrack calculateTrack(int boneIndex, int startFrame, int stopFrame, int fps) { + //preparing data for track + int framesAmount = stopFrame - startFrame; + float start = (startFrame - 1.0f) / fps; + float timeBetweenFrames = 1.0f / fps; + + float[] times = new float[framesAmount + 1]; + Vector3f[] translations = new Vector3f[framesAmount + 1]; + float[] translation = new float[3]; + Quaternion[] rotations = new Quaternion[framesAmount + 1]; + float[] quaternionRotation = new float[4]; + float[] objectRotation = new float[3]; + boolean bObjectRotation = false; + Vector3f[] scales = new Vector3f[framesAmount + 1]; + float[] scale = new float[3]; + + //calculating track data + for(int frame = startFrame; frame <= stopFrame; ++frame) { + int index = frame - startFrame; + times[index] = start + (frame - 1) * timeBetweenFrames; + for(int j = 0; j < bezierCurves.length; ++j) { + double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); + switch(bezierCurves[j].getType()) { + case AC_LOC_X: + case AC_LOC_Y: + case AC_LOC_Z: + translation[bezierCurves[j].getType() - 1] = (float)value; + break; + case OB_ROT_X: + case OB_ROT_Y: + case OB_ROT_Z: + objectRotation[bezierCurves[j].getType() - 7] = (float)value; + bObjectRotation = true; + break; + case AC_SIZE_X: + case AC_SIZE_Y: + case AC_SIZE_Z: + scale[bezierCurves[j].getType() - 13] = (float)value; + break; + case AC_QUAT_W: + quaternionRotation[3] = (float)value; + break; + case AC_QUAT_X: + case AC_QUAT_Y: + case AC_QUAT_Z: + quaternionRotation[bezierCurves[j].getType() - 26] = (float)value; + break; + default: + //TODO: error? info? warning? + } + } + translations[index] = new Vector3f(translation[0], translation[1], translation[2]); + rotations[index] = bObjectRotation ? new Quaternion().fromAngles(objectRotation) : + new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]); + scales[index] = new Vector3f(scale[0], scale[1], scale[2]); + } + calculatedTrack = new BoneTrack(boneIndex, times, translations, rotations, scales); + return calculatedTrack; + } +} \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/structures/Modifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/structures/Modifier.java new file mode 100644 index 000000000..f53fddef3 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/structures/Modifier.java @@ -0,0 +1,56 @@ +package com.jme3.scene.plugins.blender.structures; + +/** + * This class represents an object's modifier. The modifier object can be varied and the user needs to know what is + * the type of it for the specified type name. For example "ArmatureModifierData" type specified in blender is + * represented by AnimData object from jMonkeyEngine. + * @author Marcin Roguski (Kaelthas) + */ +public class Modifier { + public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData"; + public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData"; + public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData"; + + /** Blender's type of modifier. */ + private String type; + /** JME modifier representation object. */ + private Object jmeModifierRepresentation; + /** Various additional data used by modifiers.*/ + private Object additionalData; + /** + * Constructor. Creates modifier object. + * @param type + * blender's type of modifier + * @param modifier + * JME modifier representation object + */ + public Modifier(String type, Object modifier, Object additionalData) { + this.type = type; + this.jmeModifierRepresentation = modifier; + this.additionalData = additionalData; + } + + /** + * This method returns JME modifier representation object. + * @return JME modifier representation object + */ + public Object getJmeModifierRepresentation() { + return jmeModifierRepresentation; + } + + /** + * This method returns blender's type of modifier. + * @return blender's type of modifier + */ + public String getType() { + return type; + } + + /** + * This method returns additional data stored in the modifier. + * @return the additional data stored in the modifier + */ + public Object getAdditionalData() { + return additionalData; + } +} \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java new file mode 100644 index 000000000..efa507deb --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/AbstractBlenderHelper.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.utils; + +import java.nio.FloatBuffer; +import java.util.List; + +import com.jme3.util.BufferUtils; + +/** + * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can + * hold the state of the calculations. + * @author Marcin Roguski + */ +public abstract class AbstractBlenderHelper { + /** The version of the blend file. */ + protected final int blenderVersion; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender + * versions. + * @param blenderVersion + * the version read from the blend file + */ + public AbstractBlenderHelper(String blenderVersion) { + this.blenderVersion = Integer.parseInt(blenderVersion); + } + + /** + * This method clears the state of the helper so that it can be used for different calculations of another feature. + */ + public void clearState() { } + + /** + * This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are + * being created and stored in the memory. It can be unwise especially inside loops. + * @param text + * the text to be checked + * @return true if the text is blank and false otherwise + */ + protected boolean isBlank(String text) { + if (text != null) { + for (int i = 0; i < text.length(); ++i) { + if (!Character.isWhitespace(text.charAt(i))) { + return false; + } + } + } + return true; + } + + /** + * Generate a new FloatBuffer using the given array of float[4] objects. The FloatBuffer will be 4 * data.length + * long and contain the vector data as data[0][0], data[0][1], data[0][2], data[0][3], data[1][0]... etc. + * @param data + * list of float[4] objects to place into a new FloatBuffer + */ + protected FloatBuffer createFloatBuffer(List data) { + if(data == null) { + return null; + } + FloatBuffer buff = BufferUtils.createFloatBuffer(4 * data.size()); + for(float[] v : data) { + if(v != null) { + buff.put(v[0]).put(v[1]).put(v[2]).put(v[3]); + } else { + buff.put(0).put(0).put(0).put(0); + } + } + buff.flip(); + return buff; + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/BlenderInputStream.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/BlenderInputStream.java new file mode 100644 index 000000000..73b74daeb --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/BlenderInputStream.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.utils; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.blender.exception.BlenderFileException; + +/** + * An input stream with random access to data. + * @author Marcin Roguski + */ +public class BlenderInputStream extends InputStream { + private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName()); + + /** The default size of the blender buffer. */ + private static final int DEFAULT_BUFFER_SIZE = 1048576; //1MB + /** The application's asset manager. */ + private AssetManager assetManager; + /** + * Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes. + */ + private int pointerSize; + /** + * Type of byte ordering used; 'v' means little endian and 'V' means big endian. + */ + private char endianess; + /** Version of Blender the file was created in; '248' means version 2.48. */ + private String versionNumber; + /** The buffer we store the read data to. */ + protected byte[] cachedBuffer; + /** The total size of the stored data. */ + protected int size; + /** The current position of the read cursor. */ + protected int position; + + /** + * Constructor. The input stream is stored and used to read data. + * @param inputStream + * the stream we read data from + * @param assetManager + * the application's asset manager + * @param endianess + * type of byte ordering used; 'v' means little endian and 'V' means big endian + * @throws BlenderFileException + * this exception is thrown if the file header has some invalid data + */ + public BlenderInputStream(InputStream inputStream, AssetManager assetManager) throws BlenderFileException { + this.assetManager = assetManager; + //the size value will canche while reading the file; the available() method cannot be counted on + try { + size = inputStream.available(); + } catch (IOException e) { + size = 0; + } + if(size <= 0) { + size = BlenderInputStream.DEFAULT_BUFFER_SIZE; + } + + //buffered input stream is used here for much faster file reading + BufferedInputStream bufferedInputStream; + if(inputStream instanceof BufferedInputStream) { + bufferedInputStream = (BufferedInputStream)inputStream; + } else { + bufferedInputStream = new BufferedInputStream(inputStream); + } + + try { + this.readStreamToCache(bufferedInputStream); + } catch (IOException e) { + throw new BlenderFileException("Problems occured while caching the file!", e); + } + + try { + this.readFileHeader(); + } catch(BlenderFileException e) {//the file might be packed, don't panic, try one more time ;) + this.decompressFile(); + this.position = 0; + this.readFileHeader(); + } + } + + /** + * This method reads the whole stream into a buffer. + * @param inputStream + * the stream to read the file data from + * @throws IOException + * an exception is thrown when data read from the stream is invalid or there are problems with i/o + * operations + */ + private void readStreamToCache(InputStream inputStream) throws IOException { + int data = inputStream.read(); + cachedBuffer = new byte[size]; + size = 0;//this will count the actual size + while(data != -1) { + cachedBuffer[size++] = (byte)data; + if(size >= cachedBuffer.length) {//widen the cached array + byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)]; + System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length); + cachedBuffer = newBuffer; + } + data = inputStream.read(); + } + } + + /** + * This method is used when the blender file is gzipped. It decompresses the data and stores it back into the + * cachedBuffer field. + */ + private void decompressFile() { + GZIPInputStream gis = null; + try { + gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer)); + this.readStreamToCache(gis); + } catch (IOException e) { + throw new IllegalStateException("IO errors occured where they should NOT! " + + "The data is already buffered at this point!", e); + } finally { + try { + if(gis!=null) { + gis.close(); + } + } catch(IOException e) { + LOGGER.warning(e.getMessage()); + } + } + } + + /** + * This method loads the header from the given stream during instance creation. + * @param inputStream + * the stream we read the header from + * @throws BlenderFileException + * this exception is thrown if the file header has some invalid data + */ + private void readFileHeader() throws BlenderFileException { + byte[] identifier = new byte[7]; + int bytesRead = this.readBytes(identifier); + if(bytesRead != 7) { + throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!"); + } + String strIdentifier = new String(identifier); + if(!"BLENDER".equals(strIdentifier)) { + throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!"); + } + char pointerSizeSign = (char)this.readByte(); + if(pointerSizeSign == '-') { + pointerSize = 8; + } else if(pointerSizeSign == '_') { + pointerSize = 4; + } else { + throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign); + } + endianess = (char)this.readByte(); + if(endianess != 'v' && endianess != 'V') { + throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess); + } + byte[] versionNumber = new byte[3]; + bytesRead = this.readBytes(versionNumber); + if(bytesRead != 3) { + throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!"); + } + this.versionNumber = new String(versionNumber); + } + + @Override + public int read() throws IOException { + return this.readByte(); + } + + /** + * This method reads 1 byte from the stream. + * It works just in the way the read method does. + * It just not throw an exception because at this moment the whole file + * is loaded into buffer, so no need for IOException to be thrown. + * @return a byte from the stream (1 bytes read) + */ + public int readByte() { + return cachedBuffer[position++] & 0xFF; + } + + /** + * This method reads a bytes number big enough to fill the table. + * It does not throw exceptions so it is for internal use only. + * @param bytes + * an array to be filled with data + * @return number of read bytes (a length of array actually) + */ + private int readBytes(byte[] bytes) { + for(int i=0;i 0) { + position += bytesAmount - move; + } + } + + @Override + public void close() throws IOException { +// cachedBuffer = null; +// size = position = 0; + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/DataRepository.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/DataRepository.java new file mode 100644 index 000000000..e25a8e825 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/DataRepository.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.utils; + +import java.util.ArrayList; +import java.util.EmptyStackException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.BlenderKey; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.plugins.blender.data.DnaBlockData; +import com.jme3.scene.plugins.blender.data.FileBlockHeader; +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.structures.Ipo; +import com.jme3.scene.plugins.blender.structures.Modifier; + +/** + * The class that stores temporary data and manages it during loading the belnd file. This class is intended to be used + * in a single loading thread. It holds the state of loading operations. + * @author Marcin Roguski + */ +public class DataRepository { + /** The blender key. */ + private BlenderKey blenderKey; + /** The header of the file block. */ + private DnaBlockData dnaBlockData; + /** The input stream of the blend file. */ + private BlenderInputStream inputStream; + /** The asset manager. */ + private AssetManager assetManager; + /** A map containing the file block headers. The key is the old pointer address. */ + private Map fileBlockHeadersByOma = new HashMap(); + /** A map containing the file block headers. The key is the block code. */ + private Map> fileBlockHeadersByCode = new HashMap>(); + /** + * This map stores the loaded features by their old memory address. The first object in the value table is the + * loaded structure and the second - the structure already converted into proper data. + */ + private Map loadedFeatures = new HashMap(); + /** + * This map stores the loaded features by their name. Only features with ID structure can be stored here. + * The first object in the value table is the + * loaded structure and the second - the structure already converted into proper data. + */ + private Map loadedFeaturesByName = new HashMap(); + /** A stack that hold the parent structure of currently loaded feature. */ + private Stack parentStack = new Stack(); + /** A map storing loaded ipos. The key is the ipo's owner old memory address and the value is the ipo. */ + private Map loadedIpos = new HashMap(); + /** A list of modifiers for the specified object. */ + protected Map> modifiers = new HashMap>(); + /** A map og helpers that perform loading. */ + private Map helpers = new HashMap(); + + /** + * This method sets the blender key. + * @param blenderKey + * the blender key + */ + public void setBlenderKey(BlenderKey blenderKey) { + this.blenderKey = blenderKey; + } + + /** + * This method returns the blender key. + * @return the blender key + */ + public BlenderKey getBlenderKey() { + return blenderKey; + } + + /** + * This method sets the dna block data. + * @param dnaBlockData + * the dna block data + */ + public void setBlockData(DnaBlockData dnaBlockData) { + this.dnaBlockData = dnaBlockData; + } + + /** + * This method returns the dna block data. + * @return the dna block data + */ + public DnaBlockData getDnaBlockData() { + return dnaBlockData; + } + + /** + * This method returns the asset manager. + * @return the asset manager + */ + public AssetManager getAssetManager() { + return assetManager; + } + + /** + * This method sets the asset manager. + * @param assetManager + * the asset manager + */ + public void setAssetManager(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * This method returns the input stream of the blend file. + * @return the input stream of the blend file + */ + public BlenderInputStream getInputStream() { + return inputStream; + } + + /** + * This method sets the input stream of the blend file. + * @param inputStream + * the input stream of the blend file + */ + public void setInputStream(BlenderInputStream inputStream) { + this.inputStream = inputStream; + } + + /** + * This method adds a file block header to the map. Its old memory address is the key. + * @param oldMemoryAddress + * the address of the block header + * @param fileBlockHeader + * the block header to store + */ + public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { + fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); + List headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode())); + if(headers == null) { + headers = new ArrayList(); + fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers); + } + headers.add(fileBlockHeader); + } + + /** + * This method returns the block header of a given memory address. If the header is not present then null is + * returned. + * @param oldMemoryAddress + * the address of the block header + * @return loaded header or null if it was not yet loaded + */ + public FileBlockHeader getFileBlock(Long oldMemoryAddress) { + return fileBlockHeadersByOma.get(oldMemoryAddress); + } + + /** + * This method returns a list of file blocks' headers of a specified code. + * @param code + * the code of file blocks + * @return a list of file blocks' headers of a specified code + */ + public List getFileBlocks(Integer code) { + return fileBlockHeadersByCode.get(code); + } + + /** + * This method clears the saved block headers stored in the features map. + */ + public void clearFileBlocks() { + fileBlockHeadersByOma.clear(); + fileBlockHeadersByCode.clear(); + } + + /** + * This method adds a helper instance to the helpers' map. + * @param + * the type of the helper + * @param clazz + * helper's class definition + * @param helper + * the helper instance + */ + public void putHelper(Class clazz, AbstractBlenderHelper helper) { + helpers.put(clazz.getSimpleName(), helper); + } + + @SuppressWarnings("unchecked") + public T getHelper(Class clazz) { + return (T)helpers.get(clazz.getSimpleName()); + } + + + /** + * This method adds a loaded feature to the map. The key is its unique old memory address. + * @param oldMemoryAddress + * the address of the feature + * @param featureName the name of the feature + * @param structure + * the filled structure of the feature + * @param feature + * the feature we want to store + */ + public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) { + if(oldMemoryAddress == null || structure == null || feature == null) { + throw new IllegalArgumentException("One of the given arguments is null!"); + } + Object[] storedData = new Object[] {structure, feature}; + loadedFeatures.put(oldMemoryAddress, storedData); + if(featureName!=null) { + loadedFeaturesByName.put(featureName, storedData); + } + } + + /** + * This method returns the feature of a given memory address. If the feature is not yet loaded then null is + * returned. + * @param oldMemoryAddress + * the address of the feature + * @param loadedFeatureDataType + * the type of data we want to retreive it can be either filled structure or already converted feature + * @return loaded feature or null if it was not yet loaded + */ + public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) { + Object[] result = loadedFeatures.get(oldMemoryAddress); + if(result != null) { + return result[loadedFeatureDataType.getIndex()]; + } + return null; + } + + /** + * This method returns the feature of a given name. If the feature is not yet loaded then null is + * returned. + * @param featureName + * the name of the feature + * @param loadedFeatureDataType + * the type of data we want to retreive it can be either filled structure or already converted feature + * @return loaded feature or null if it was not yet loaded + */ + public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) { + Object[] result = loadedFeaturesByName.get(featureName); + if(result != null) { + return result[loadedFeatureDataType.getIndex()]; + } + return null; + } + + /** + * This method clears the saved features stored in the features map. + */ + public void clearLoadedFeatures() { + loadedFeatures.clear(); + } + + /** + * This method adds the structure to the parent stack. + * @param parent + * the structure to be added to the stack + */ + public void pushParent(Structure parent) { + parentStack.push(parent); + } + + /** + * This method removes the structure from the top of the parent's stack. + * @return the structure that was removed from the stack + */ + public Structure popParent() { + try { + return parentStack.pop(); + } catch(EmptyStackException e) { + return null; + } + } + + /** + * This method retreives the structure at the top of the parent's stack but does not remove it. + * @return the structure from the top of the stack + */ + public Structure peekParent() { + try { + return parentStack.peek(); + } catch(EmptyStackException e) { + return null; + } + } + + public void addIpo(Long ownerOMA, Ipo ipo) { + loadedIpos.put(ownerOMA, ipo); + } + + public Ipo removeIpo(Long ownerOma) { + return loadedIpos.remove(ownerOma); + } + + public Ipo getIpo(Long ownerOMA) { + return loadedIpos.get(ownerOMA); + } + + /** + * This method adds a new modifier to the list. + * @param ownerOMA + * the owner's old memory address + * @param modifierType + * the type of the modifier + * @param loadedModifier + * the loaded modifier object + */ + public void addModifier(Long ownerOMA, String modifierType, Object loadedModifier, Object additionalModifierData) { + List objectModifiers = this.modifiers.get(ownerOMA); + if(objectModifiers == null) { + objectModifiers = new ArrayList(); + this.modifiers.put(ownerOMA, objectModifiers); + } + objectModifiers.add(new Modifier(modifierType, loadedModifier, additionalModifierData)); + } + + /** + * This method returns modifiers for the object specified by its old memory address and the modifier type. If no + * modifiers are found - empty list is returned. If the type is null - all modifiers for the object are returned. + * @param objectOMA + * object's old memory address + * @param type + * the type of the modifier + * @return the list of object's modifiers + */ + public List getModifiers(Long objectOMA, String type) { + List result = new ArrayList(); + List readModifiers = modifiers.get(objectOMA); + if(readModifiers != null && readModifiers.size() > 0) { + for(Modifier modifier : readModifiers) { + if(type==null || type.isEmpty() || modifier.getType().equals(type)) { + result.add(modifier); + } + } + } + return result; + } + + /** + * This metod returns the default material. + * @return the default material + */ + public synchronized Material getDefaultMaterial() { + if(blenderKey.getDefaultMaterial() == null) { + Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md"); + defaultMaterial.setColor("Color", ColorRGBA.DarkGray); + blenderKey.setDefaultMaterial(defaultMaterial); + } + return blenderKey.getDefaultMaterial(); + } + + /** + * This enum defines what loaded data type user wants to retreive. It can be either filled structure or already + * converted data. + * @author Marcin Roguski + */ + public static enum LoadedFeatureDataType { + LOADED_STRUCTURE(0), LOADED_FEATURE(1); + + private int index; + + private LoadedFeatureDataType(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/DynamicArray.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/DynamicArray.java new file mode 100644 index 000000000..845a43409 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/DynamicArray.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.utils; + +import com.jme3.scene.plugins.blender.exception.BlenderFileException; + +/** + * An array that can be dynamically modified/ + * @author Marcin Roguski + * @param + * the type of stored data in the array + */ +public class DynamicArray implements Cloneable { + /** An array object that holds the required data. */ + private T[] array; + /** + * This table holds the sizes of dimetions of the dynamic table. It's length specifies the table dimension or a + * pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths: + * dynTable[a][b][c], where a,b,c are stored in the tableSizes table. + */ + private int[] tableSizes; + + /** + * Constructor. Builds an empty array of the specified sizes. + * @param tableSizes + * the sizes of the table + * @throws BlenderFileException + * an exception is thrown if one of the sizes is not a positive number + */ + @SuppressWarnings("unchecked") + public DynamicArray(int[] tableSizes) throws BlenderFileException { + this.tableSizes = tableSizes; + int totalSize = 1; + for(int size : tableSizes) { + if(size <= 0) { + throw new BlenderFileException("The size of the table must be positive!"); + } + totalSize *= size; + } + this.array = (T[])new Object[totalSize]; + } + + /** + * Constructor. Builds an empty array of the specified sizes. + * @param tableSizes + * the sizes of the table + * @throws BlenderFileException + * an exception is thrown if one of the sizes is not a positive number + */ + public DynamicArray(int[] tableSizes, T[] data) throws BlenderFileException { + this.tableSizes = tableSizes; + int totalSize = 1; + for(int size : tableSizes) { + if(size <= 0) { + throw new BlenderFileException("The size of the table must be positive!"); + } + totalSize *= size; + } + if(totalSize != data.length) { + throw new IllegalArgumentException("The size of the table does not match the size of the given data!"); + } + this.array = data; + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + /** + * This method returns a value on the specified position. The dimension of the table is not taken into + * consideration. + * @param position + * the position of the data + * @return required data + */ + public T get(int position) { + return array[position]; + } + + /** + * This method returns a value on the specified position in multidimensional array. Be careful not to exceed the + * table boundaries. Check the table's dimension first. + * @param position + * the position of the data indices of data position + * @return required data required data + */ + public T get(int... position) { + if(position.length != tableSizes.length) { + throw new ArrayIndexOutOfBoundsException("The table accepts " + tableSizes.length + " indexing number(s)!"); + } + int index = 0; + for(int i = 0; i < position.length - 1; ++i) { + index += position[i] * tableSizes[i + 1]; + } + index += position[position.length - 1]; + return array[index]; + } + + /** + * This method returns the total amount of data stored in the array. + * @return the total amount of data stored in the array + */ + public int getTotalSize() { + return array.length; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + if(array instanceof Character[]) {//in case of character array we convert it to String + for(int i = 0; i < array.length && (Character)array[i] != '\0'; ++i) {//strings are terminater with '0' + result.append(array[i]); + } + } else { + result.append('['); + for(int i = 0; i < array.length; ++i) { + result.append(array[i].toString()); + if(i + 1 < array.length) { + result.append(','); + } + } + result.append(']'); + } + return result.toString(); + } +} \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/IBlenderConverter.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/IBlenderConverter.java new file mode 100644 index 000000000..59c5d9a8b --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/IBlenderConverter.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.utils; + +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.exception.BlenderFileException; + +/** + * This interface provides an abstraction to converting loaded blender structures into data structures. The data + * structures can vary and therefore one can use the loader for different kind of engines. + * @author Marcin Roguski + * @param + * the type of the scene node element + * @param + * the type of camera element + * @param + * the type of light element + * @param + * the type of object element + * @param + * the type of mesh element + * @param + * the type of material element + */ +//TODO: ujednolicić wyrzucane wyjątki +public interface IBlenderConverter { + /** + * This method reads converts the given structure into scene. The given structure needs to be filled with the + * appropriate data. + * @param structure + * the structure we read the scene from + * @return the scene feature + */ + NodeType toScene(Structure structure); + + /** + * This method reads converts the given structure into camera. The given structure needs to be filled with the + * appropriate data. + * @param structure + * the structure we read the camera from + * @return the camera feature + */ + CameraType toCamera(Structure structure) throws BlenderFileException; + + /** + * This method reads converts the given structure into light. The given structure needs to be filled with the + * appropriate data. + * @param structure + * the structure we read the light from + * @return the light feature + */ + LightType toLight(Structure structure) throws BlenderFileException; + + /** + * This method reads converts the given structure into objct. The given structure needs to be filled with the + * appropriate data. + * @param structure + * the structure we read the object from + * @return the object feature + */ + ObjectType toObject(Structure structure) throws BlenderFileException; + + /** + * This method reads converts the given structure into mesh. The given structure needs to be filled with the + * appropriate data. + * @param structure + * the structure we read the mesh from + * @return the mesh feature + */ + MeshType toMesh(Structure structure) throws BlenderFileException; + + /** + * This method reads converts the given structure into material. The given structure needs to be filled with the + * appropriate data. + * @param structure + * the structure we read the material from + * @return the material feature + */ + MaterialType toMaterial(Structure structure) throws BlenderFileException; +} \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/JmeConverter.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/JmeConverter.java new file mode 100644 index 000000000..462578da8 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/JmeConverter.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.utils; + +import java.util.List; +import java.util.logging.Logger; + +import com.jme3.asset.BlenderKey.FeaturesToLoad; +import com.jme3.asset.BlenderKey.WorldData; +import com.jme3.light.AmbientLight; +import com.jme3.light.Light; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.exception.BlenderFileException; +import com.jme3.scene.plugins.blender.helpers.CameraHelper; +import com.jme3.scene.plugins.blender.helpers.LightHelper; +import com.jme3.scene.plugins.blender.helpers.MaterialHelper; +import com.jme3.scene.plugins.blender.helpers.MeshHelper; +import com.jme3.scene.plugins.blender.helpers.ObjectHelper; + +/** + * This class converts blender file blocks into jMonkeyEngine data structures. + * @author Marcin Roguski + */ +public class JmeConverter implements IBlenderConverter, Material> { + private static final Logger LOGGER = Logger.getLogger(JmeConverter.class.getName()); + + private final DataRepository dataRepository; + + /** + * Constructor. Creates the loader and checks if the given data is correct. + * @param dataRepository + * the data repository; it should have the following field set: - asset manager - blender key - dna block + * data - blender input stream Otherwise IllegalArgumentException will be thrown. + * @param featuresToLoad + * bitwise flag describing what features are to be loaded + * @see FeaturesToLoad FeaturesToLoad + */ + public JmeConverter(DataRepository dataRepository) { + //validating the given data first + if(dataRepository.getAssetManager() == null) { + throw new IllegalArgumentException("Cannot find asset manager!"); + } + if(dataRepository.getBlenderKey() == null) { + throw new IllegalArgumentException("Cannot find blender key!"); + } + if(dataRepository.getDnaBlockData() == null) { + throw new IllegalArgumentException("Cannot find dna block!"); + } + if(dataRepository.getInputStream() == null) { + throw new IllegalArgumentException("Cannot find blender file stream!"); + } + this.dataRepository = dataRepository; + } + + @Override + public Node toScene(Structure structure) {//TODO: poprawny import sceny + if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) { + return null; + } + Structure id = (Structure)structure.getFieldValue("id"); + String sceneName = id.getFieldValue("name").toString(); + return new Node(sceneName); + } + + @Override + public Camera toCamera(Structure structure) throws BlenderFileException { + if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) == 0) { + return null; + } + CameraHelper cameraHelper = dataRepository.getHelper(CameraHelper.class); + return cameraHelper.toCamera(structure); + } + + @Override + public Light toLight(Structure structure) throws BlenderFileException { + if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) == 0) { + return null; + } + LightHelper lightHelper = dataRepository.getHelper(LightHelper.class); + return lightHelper.toLight(structure, dataRepository); + } + + @Override + public Object toObject(Structure structure) throws BlenderFileException { + if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) == 0) { + return null; + } + ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); + return objectHelper.toObject(structure, dataRepository); + } + + @Override + public List toMesh(Structure structure) throws BlenderFileException { + MeshHelper meshHelper = dataRepository.getHelper(MeshHelper.class); + return meshHelper.toMesh(structure, dataRepository); + } + + @Override + public Material toMaterial(Structure structure) throws BlenderFileException { + if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) == 0) { + return null; + } + MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); + return materialHelper.toMaterial(structure, dataRepository); + } + + /** + * This method returns the data read from the WORLD file block. The block contains data that can be stored as + * separate jme features and therefore cannot be returned as a single jME scene feature. + * @param structure + * the structure with WORLD block data + * @return data read from the WORLD block that can be added to the scene + */ + public WorldData toWorldData(Structure structure) { + WorldData result = new WorldData(); + + //reading ambient light + AmbientLight ambientLight = new AmbientLight(); + float ambr = ((Number)structure.getFieldValue("ambr")).floatValue(); + float ambg = ((Number)structure.getFieldValue("ambg")).floatValue(); + float ambb = ((Number)structure.getFieldValue("ambb")).floatValue(); + ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f)); + result.setAmbientLight(ambientLight); + + return result; + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/utils/Pointer.java b/engine/src/blender/com/jme3/scene/plugins/blender/utils/Pointer.java new file mode 100644 index 000000000..dd82c6f35 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/utils/Pointer.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.utils; + +import java.util.ArrayList; +import java.util.List; + +import com.jme3.scene.plugins.blender.data.FileBlockHeader; +import com.jme3.scene.plugins.blender.data.Structure; +import com.jme3.scene.plugins.blender.exception.BlenderFileException; + +/** + * A class that represents a pointer of any level that can be stored in the file. + * @author Marcin Roguski + */ +public class Pointer { + /** The data repository. */ + private DataRepository dataRepository; + /** The level of the pointer. */ + private int pointerLevel; + /** The address in file it points to. */ + private long oldMemoryAddress; + /** This variable indicates if the field is a function pointer. */ + public boolean function; + + /** + * Constructr. Stores the basic data about the pointer. + * @param pointerLevel + * the level of the pointer + * @param function + * this variable indicates if the field is a function pointer + * @param dataRepository + * the repository f data; used in fetching the value that the pointer points + */ + public Pointer(int pointerLevel, boolean function, DataRepository dataRepository) { + this.pointerLevel = pointerLevel; + this.function = function; + this.dataRepository = dataRepository; + } + + /** + * This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method + * for this. + * @param inputStream + * the stream we read the pointer value from + */ + public void fill(BlenderInputStream inputStream) { + oldMemoryAddress = inputStream.readPointer(); + } + + /** + * This method fetches the data stored under the given address. + * @param inputStream + * the stream we read data from + * @param dataIndices + * the offset of the data in the table pointed by the pointer + * @return the data read from the file + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + public List fetchData(BlenderInputStream inputStream) throws BlenderFileException { + if(oldMemoryAddress == 0) { + throw new NullPointerException("The pointer points to nothing!"); + } + List structures = null; + FileBlockHeader dataFileBlock = dataRepository.getFileBlock(oldMemoryAddress); + if(pointerLevel > 1) { + int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount(); + for(int i = 0; i < pointersAmount; ++i) { + inputStream.setPosition(dataFileBlock.getBlockPosition() + inputStream.getPointerSize() * i); + long oldMemoryAddress = inputStream.readPointer(); + if(oldMemoryAddress != 0L) { + Pointer p = new Pointer(pointerLevel - 1, this.function, dataRepository); + p.oldMemoryAddress = oldMemoryAddress; + if(structures == null) { + structures = p.fetchData(inputStream); + } else { + structures.addAll(p.fetchData(inputStream)); + } + } + } + } else { + inputStream.setPosition(dataFileBlock.getBlockPosition()); + structures = new ArrayList(dataFileBlock.getCount()); + for(int i = 0; i < dataFileBlock.getCount(); ++i) { + Structure structure = dataRepository.getDnaBlockData().getStructure(dataFileBlock.getSdnaIndex()); + structure.fill(inputStream); + structures.add(structure); + } + return structures; + } + return structures; + } + + /** + * This method indicates if this pointer points to a function. + * @return true if this is a function pointer and false otherwise + */ + public boolean isFunction() { + return function; + } + + /** + * This method indicates if this is a null-pointer or not. + * @return true if the pointer is null and false otherwise + */ + public boolean isNull() { + return oldMemoryAddress == 0; + } + + /** + * This method returns the old memory address of the structure pointed by the pointer. + * @return the old memory address of the structure pointed by the pointer + */ + public long getOldMemoryAddress() { + return oldMemoryAddress; + } + + @Override + public String toString() { + return oldMemoryAddress == 0 ? "{$null$}" : "{$" + oldMemoryAddress + "$}"; + } + + @Override + public int hashCode() { + return 31 + (int)(oldMemoryAddress ^ oldMemoryAddress >>> 32); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj == null) { + return false; + } + if(this.getClass() != obj.getClass()) { + return false; + } + Pointer other = (Pointer)obj; + if(oldMemoryAddress != other.oldMemoryAddress) { + return false; + } + return true; + } +} diff --git a/engine/src/desktop/com/jme3/asset/Desktop.cfg b/engine/src/desktop/com/jme3/asset/Desktop.cfg index 55bacc779..8aa390192 100644 --- a/engine/src/desktop/com/jme3/asset/Desktop.cfg +++ b/engine/src/desktop/com/jme3/asset/Desktop.cfg @@ -17,4 +17,5 @@ LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene +LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib \ No newline at end of file