git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7554 75d07b2b-3a1a-0410-a2c5-0572b91ccdca3.0
parent
39e3e2d11f
commit
36d041fae6
@ -1,113 +1,114 @@ |
|||||||
annotation.processing.enabled=false |
annotation.processing.enabled=false |
||||||
annotation.processing.enabled.in.editor=false |
annotation.processing.enabled.in.editor=false |
||||||
annotation.processing.run.all.processors=true |
annotation.processing.run.all.processors=true |
||||||
ant.customtasks.libs=JWSAntTasks |
ant.customtasks.libs=JWSAntTasks |
||||||
application.homepage=http://www.jmonkeyengine.com/ |
application.homepage=http://www.jmonkeyengine.com/ |
||||||
application.title=jMonkeyEngine 3.0 |
application.title=jMonkeyEngine 3.0 |
||||||
application.vendor=jMonkeyEngine |
application.vendor=jMonkeyEngine |
||||||
build.classes.dir=${build.dir}/classes |
build.classes.dir=${build.dir}/classes |
||||||
build.classes.excludes=**/*.java,**/*.form |
build.classes.excludes=**/*.java,**/*.form |
||||||
# This directory is removed when the project is cleaned: |
# This directory is removed when the project is cleaned: |
||||||
build.dir=build |
build.dir=build |
||||||
build.generated.dir=${build.dir}/generated |
build.generated.dir=${build.dir}/generated |
||||||
build.generated.sources.dir=${build.dir}/generated-sources |
build.generated.sources.dir=${build.dir}/generated-sources |
||||||
# Only compile against the classpath explicitly listed here: |
# Only compile against the classpath explicitly listed here: |
||||||
build.sysclasspath=ignore |
build.sysclasspath=ignore |
||||||
build.test.classes.dir=${build.dir}/test/classes |
build.test.classes.dir=${build.dir}/test/classes |
||||||
build.test.results.dir=${build.dir}/test/results |
build.test.results.dir=${build.dir}/test/results |
||||||
# Uncomment to specify the preferred debugger connection transport: |
# Uncomment to specify the preferred debugger connection transport: |
||||||
#debug.transport=dt_socket |
#debug.transport=dt_socket |
||||||
debug.classpath=\ |
debug.classpath=\ |
||||||
${run.classpath} |
${run.classpath} |
||||||
debug.test.classpath=\ |
debug.test.classpath=\ |
||||||
${run.test.classpath} |
${run.test.classpath} |
||||||
# This directory is removed when the project is cleaned: |
# This directory is removed when the project is cleaned: |
||||||
dist.dir=dist |
dist.dir=dist |
||||||
dist.jar=${dist.dir}/jMonkeyEngine3.jar |
dist.jar=${dist.dir}/jMonkeyEngine3.jar |
||||||
dist.javadoc.dir=${dist.dir}/javadoc |
dist.javadoc.dir=${dist.dir}/javadoc |
||||||
endorsed.classpath= |
endorsed.classpath= |
||||||
excludes= |
excludes= |
||||||
file.reference.src-test-data=src/test-data |
file.reference.src-test-data=src/test-data |
||||||
includes=** |
includes=** |
||||||
jar.archive.disabled=${jnlp.enabled} |
jar.archive.disabled=${jnlp.enabled} |
||||||
jar.compress=true |
jar.compress=true |
||||||
jar.index=${jnlp.enabled} |
jar.index=${jnlp.enabled} |
||||||
javac.classpath=\ |
javac.classpath=\ |
||||||
${libs.jogg.classpath}:\ |
${libs.jogg.classpath}:\ |
||||||
${libs.jbullet.classpath}:\ |
${libs.jbullet.classpath}:\ |
||||||
${libs.bullet.classpath}:\ |
${libs.bullet.classpath}:\ |
||||||
${libs.lwjgl.classpath}:\ |
${libs.lwjgl.classpath}:\ |
||||||
${libs.jheora.classpath}:\ |
${libs.jheora.classpath}:\ |
||||||
${libs.niftygui1.3.classpath}:\ |
${libs.niftygui1.3.classpath}:\ |
||||||
${libs.jme3-test-data.classpath}:\ |
${libs.jme3-test-data.classpath}:\ |
||||||
${libs.noise.classpath} |
${libs.noise.classpath} |
||||||
# Space-separated list of extra javac options |
# Space-separated list of extra javac options |
||||||
javac.compilerargs= |
javac.compilerargs= |
||||||
javac.deprecation=false |
javac.deprecation=false |
||||||
javac.processorpath=\ |
javac.processorpath=\ |
||||||
${javac.classpath} |
${javac.classpath} |
||||||
javac.source=1.5 |
javac.source=1.5 |
||||||
javac.target=1.5 |
javac.target=1.5 |
||||||
javac.test.classpath=\ |
javac.test.classpath=\ |
||||||
${javac.classpath}:\ |
${javac.classpath}:\ |
||||||
${build.classes.dir}:\ |
${build.classes.dir}:\ |
||||||
${libs.junit_4.classpath} |
${libs.junit_4.classpath} |
||||||
javadoc.additionalparam= |
javadoc.additionalparam= |
||||||
javadoc.author=false |
javadoc.author=false |
||||||
javadoc.encoding=${source.encoding} |
javadoc.encoding=${source.encoding} |
||||||
javadoc.noindex=false |
javadoc.noindex=false |
||||||
javadoc.nonavbar=false |
javadoc.nonavbar=false |
||||||
javadoc.notree=false |
javadoc.notree=false |
||||||
javadoc.private=false |
javadoc.private=false |
||||||
javadoc.splitindex=true |
javadoc.splitindex=true |
||||||
javadoc.use=true |
javadoc.use=true |
||||||
javadoc.version=false |
javadoc.version=false |
||||||
javadoc.windowtitle=jMonkeyEngine3 |
javadoc.windowtitle=jMonkeyEngine3 |
||||||
jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api" |
jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api" |
||||||
jnlp.applet.class=jme3test.awt.AppHarness |
jnlp.applet.class=jme3test.awt.AppHarness |
||||||
jnlp.applet.height=300 |
jnlp.applet.height=300 |
||||||
jnlp.applet.width=300 |
jnlp.applet.width=300 |
||||||
jnlp.codebase.type=user |
jnlp.codebase.type=user |
||||||
jnlp.codebase.user=http://jmonkeyengine.com/javawebstart/ |
jnlp.codebase.user=http://jmonkeyengine.com/javawebstart/ |
||||||
jnlp.descriptor=application |
jnlp.descriptor=application |
||||||
jnlp.enabled=false |
jnlp.enabled=false |
||||||
jnlp.icon=/Users/normenhansen/Pictures/jme/icons/jme-logo48.png |
jnlp.icon=/Users/normenhansen/Pictures/jme/icons/jme-logo48.png |
||||||
jnlp.mixed.code=default |
jnlp.mixed.code=default |
||||||
jnlp.offline-allowed=true |
jnlp.offline-allowed=true |
||||||
jnlp.signed=true |
jnlp.signed=true |
||||||
jnlp.signing=generated |
jnlp.signing=generated |
||||||
jnlp.signing.alias= |
jnlp.signing.alias= |
||||||
jnlp.signing.keystore= |
jnlp.signing.keystore= |
||||||
main.class=jme3test.TestChooser |
main.class=jme3test.TestChooser |
||||||
manifest.file=MANIFEST.MF |
manifest.file=MANIFEST.MF |
||||||
meta.inf.dir=${src.dir}/META-INF |
meta.inf.dir=${src.dir}/META-INF |
||||||
mkdist.disabled=false |
mkdist.disabled=false |
||||||
platform.active=default_platform |
platform.active=default_platform |
||||||
run.classpath=\ |
run.classpath=\ |
||||||
${javac.classpath}:\ |
${javac.classpath}:\ |
||||||
${build.classes.dir} |
${build.classes.dir} |
||||||
run.jvmargs=-Xms40m -Xmx40m -XX:MaxDirectMemorySize=256M |
run.jvmargs=-Xms40m -Xmx40m -XX:MaxDirectMemorySize=256M |
||||||
run.test.classpath=\ |
run.test.classpath=\ |
||||||
${javac.test.classpath}:\ |
${javac.test.classpath}:\ |
||||||
${build.test.classes.dir} |
${build.test.classes.dir} |
||||||
source.encoding=UTF-8 |
source.encoding=UTF-8 |
||||||
src.core-data.dir=src/core-data |
src.blender.dir=src/blender |
||||||
src.core-plugins.dir=src/core-plugins |
src.core-data.dir=src/core-data |
||||||
src.core.dir=src/core |
src.core-plugins.dir=src/core-plugins |
||||||
src.desktop-fx.dir=src/desktop-fx |
src.core.dir=src/core |
||||||
src.desktop.dir=src/desktop |
src.desktop-fx.dir=src/desktop-fx |
||||||
src.games.dir=src/games |
src.desktop.dir=src/desktop |
||||||
src.jbullet.dir=src/jbullet |
src.games.dir=src/games |
||||||
src.jheora.dir=src/jheora |
src.jbullet.dir=src/jbullet |
||||||
src.jogg.dir=src/jogg |
src.jheora.dir=src/jheora |
||||||
src.lwjgl-oal.dir=src/lwjgl-oal |
src.jogg.dir=src/jogg |
||||||
src.lwjgl-ogl.dir=src/lwjgl-ogl |
src.lwjgl-oal.dir=src/lwjgl-oal |
||||||
src.networking.dir=src\\networking |
src.lwjgl-ogl.dir=src/lwjgl-ogl |
||||||
src.niftygui.dir=src/niftygui |
src.networking.dir=src\\networking |
||||||
src.ogre.dir=src/ogre |
src.niftygui.dir=src/niftygui |
||||||
src.pack.dir=src/pack |
src.ogre.dir=src/ogre |
||||||
src.terrain.dir=src/terrain |
src.pack.dir=src/pack |
||||||
src.test.dir=src/test |
src.terrain.dir=src/terrain |
||||||
src.tools.dir=src/tools |
src.test.dir=src/test |
||||||
src.xml.dir=src/xml |
src.tools.dir=src/tools |
||||||
test.test.dir=test |
src.xml.dir=src/xml |
||||||
|
test.test.dir=test |
||||||
|
@ -0,0 +1,756 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.asset; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.Queue; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import com.jme3.bounding.BoundingVolume; |
||||||
|
import com.jme3.collision.Collidable; |
||||||
|
import com.jme3.collision.CollisionResults; |
||||||
|
import com.jme3.collision.UnsupportedCollisionException; |
||||||
|
import com.jme3.export.InputCapsule; |
||||||
|
import com.jme3.export.JmeExporter; |
||||||
|
import com.jme3.export.JmeImporter; |
||||||
|
import com.jme3.export.OutputCapsule; |
||||||
|
import com.jme3.light.AmbientLight; |
||||||
|
import com.jme3.light.Light; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.material.RenderState.FaceCullMode; |
||||||
|
import com.jme3.renderer.Camera; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.SceneGraphVisitor; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.plugins.ogre.AnimData; |
||||||
|
import com.jme3.texture.Texture; |
||||||
|
|
||||||
|
/** |
||||||
|
* Blender key. Contains path of the blender file and its loading properties. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class BlenderKey extends ModelKey { |
||||||
|
protected static final int DEFAULT_FPS = 25; |
||||||
|
|
||||||
|
/** |
||||||
|
* Animation definitions. The key is the object name that owns the animation. The value is a map between animation |
||||||
|
* name and its start and stop frames. Blender stores a pointer for animation within object. Therefore one object |
||||||
|
* can only have one animation at the time. We want to be able to switch between animations for one object so we |
||||||
|
* need to map the object name to animation names the object will use. |
||||||
|
*/ |
||||||
|
protected Map<String, Map<String, int[]>> animations; |
||||||
|
/** |
||||||
|
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time |
||||||
|
* between the frames. |
||||||
|
*/ |
||||||
|
protected int fps = DEFAULT_FPS; |
||||||
|
/** Width of generated textures (in pixels). Blender uses 140x140 by default. */ |
||||||
|
protected int generatedTextureWidth = 140; |
||||||
|
/** Height of generated textures (in pixels). Blender uses 140x140 by default. */ |
||||||
|
protected int generatedTextureHeight = 140; |
||||||
|
/** |
||||||
|
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. |
||||||
|
*/ |
||||||
|
protected int featuresToLoad = FeaturesToLoad.ALL; |
||||||
|
/** The root path for all the assets. */ |
||||||
|
protected String assetRootPath; |
||||||
|
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */ |
||||||
|
protected boolean fixUpAxis = true; |
||||||
|
/** |
||||||
|
* The name of world settings that the importer will use. If not set or specified name does not occur in the file |
||||||
|
* then the first world settings in the file will be used. |
||||||
|
*/ |
||||||
|
protected String usedWorld; |
||||||
|
/** |
||||||
|
* User's default material that is set fo objects that have no material definition in blender. The default value is |
||||||
|
* null. If the value is null the importer will use its own default material (gray color - like in blender). |
||||||
|
*/ |
||||||
|
protected Material defaultMaterial; |
||||||
|
/** Face cull mode. By default it is disabled. */ |
||||||
|
protected FaceCullMode faceCullMode = FaceCullMode.Off; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor used by serialization mechanisms. |
||||||
|
*/ |
||||||
|
public BlenderKey() {} |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Creates a key for the given file name. |
||||||
|
* @param name |
||||||
|
* the name (path) of a file |
||||||
|
*/ |
||||||
|
public BlenderKey(String name) { |
||||||
|
super(name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds an animation definition. If a definition already eixists in the key then it is replaced. |
||||||
|
* @param objectName |
||||||
|
* the name of animation's owner |
||||||
|
* @param name |
||||||
|
* the name of the animation |
||||||
|
* @param start |
||||||
|
* the start frame of the animation |
||||||
|
* @param stop |
||||||
|
* the stop frame of the animation |
||||||
|
*/ |
||||||
|
public synchronized void addAnimation(String objectName, String name, int start, int stop) { |
||||||
|
if(objectName == null) { |
||||||
|
throw new IllegalArgumentException("Object name cannot be null!"); |
||||||
|
} |
||||||
|
if(name == null) { |
||||||
|
throw new IllegalArgumentException("Animation name cannot be null!"); |
||||||
|
} |
||||||
|
if(start > stop) { |
||||||
|
throw new IllegalArgumentException("Start frame cannot be greater than stop frame!"); |
||||||
|
} |
||||||
|
if(animations == null) { |
||||||
|
animations = new HashMap<String, Map<String, int[]>>(); |
||||||
|
animations.put(objectName, new HashMap<String, int[]>()); |
||||||
|
} |
||||||
|
Map<String, int[]> objectAnimations = animations.get(objectName); |
||||||
|
if(objectAnimations == null) { |
||||||
|
objectAnimations = new HashMap<String, int[]>(); |
||||||
|
animations.put(objectName, objectAnimations); |
||||||
|
} |
||||||
|
objectAnimations.put(name, new int[] {start, stop}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the animation frames boundaries. |
||||||
|
* @param objectName |
||||||
|
* the name of animation's owner |
||||||
|
* @param name |
||||||
|
* animation name |
||||||
|
* @return animation frame boundaries in a table [start, stop] or null if animation of the given name does not |
||||||
|
* exists |
||||||
|
*/ |
||||||
|
public int[] getAnimationFrames(String objectName, String name) { |
||||||
|
Map<String, int[]> objectAnimations = animations == null ? null : animations.get(objectName); |
||||||
|
int[] frames = objectAnimations == null ? null : objectAnimations.get(name); |
||||||
|
return frames == null ? null : frames.clone(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the animation names for the given object name. |
||||||
|
* @param objectName |
||||||
|
* the name of the object |
||||||
|
* @return an array of animations for this object |
||||||
|
*/ |
||||||
|
public Set<String> getAnimationNames(String objectName) { |
||||||
|
Map<String, int[]> objectAnimations = animations == null ? null : animations.get(objectName); |
||||||
|
return objectAnimations == null ? null : objectAnimations.keySet(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the animations map. |
||||||
|
* @return the animations map |
||||||
|
*/ |
||||||
|
public Map<String, Map<String, int[]>> getAnimations() { |
||||||
|
return animations; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25. |
||||||
|
* @return the frames per second amount |
||||||
|
*/ |
||||||
|
public int getFps() { |
||||||
|
return fps; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets frames per second amount. |
||||||
|
* @param fps |
||||||
|
* the frames per second amount |
||||||
|
*/ |
||||||
|
public void setFps(int fps) { |
||||||
|
this.fps = fps; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the width of generated texture (in pixels). By default the value is 140 px. |
||||||
|
* @param generatedTextureWidth |
||||||
|
* the width of generated texture |
||||||
|
*/ |
||||||
|
public void setGeneratedTextureWidth(int generatedTextureWidth) { |
||||||
|
this.generatedTextureWidth = generatedTextureWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the width of generated texture (in pixels). By default the value is 140 px. |
||||||
|
* @return the width of generated texture |
||||||
|
*/ |
||||||
|
public int getGeneratedTextureWidth() { |
||||||
|
return generatedTextureWidth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the height of generated texture (in pixels). By default the value is 140 px. |
||||||
|
* @param generatedTextureHeight |
||||||
|
* the height of generated texture |
||||||
|
*/ |
||||||
|
public void setGeneratedTextureHeight(int generatedTextureHeight) { |
||||||
|
this.generatedTextureHeight = generatedTextureHeight; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the height of generated texture (in pixels). By default the value is 140 px. |
||||||
|
* @return the height of generated texture |
||||||
|
*/ |
||||||
|
public int getGeneratedTextureHeight() { |
||||||
|
return generatedTextureHeight; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the face cull mode. |
||||||
|
* @return the face cull mode |
||||||
|
*/ |
||||||
|
public FaceCullMode getFaceCullMode() { |
||||||
|
return faceCullMode; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the face cull mode. |
||||||
|
* @param faceCullMode |
||||||
|
* the face cull mode |
||||||
|
*/ |
||||||
|
public void setFaceCullMode(FaceCullMode faceCullMode) { |
||||||
|
this.faceCullMode = faceCullMode; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the asset root path. |
||||||
|
* @param assetRootPath |
||||||
|
* the assets root path |
||||||
|
*/ |
||||||
|
public void setAssetRootPath(String assetRootPath) { |
||||||
|
this.assetRootPath = assetRootPath; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the asset root path. |
||||||
|
* @return the asset root path |
||||||
|
*/ |
||||||
|
public String getAssetRootPath() { |
||||||
|
return assetRootPath; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds features to be loaded. |
||||||
|
* @param featuresToLoad |
||||||
|
* bitwise flag of FeaturesToLoad interface values |
||||||
|
*/ |
||||||
|
public void includeInLoading(int featuresToLoad) { |
||||||
|
this.featuresToLoad |= featuresToLoad; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method removes features from being loaded. |
||||||
|
* @param featuresToLoad |
||||||
|
* bitwise flag of FeaturesToLoad interface values |
||||||
|
*/ |
||||||
|
public void excludeFromLoading(int featuresNotToLoad) { |
||||||
|
this.featuresToLoad &= ~featuresNotToLoad; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by |
||||||
|
* the blender file loader. |
||||||
|
* @return features that will be loaded by the blender file loader |
||||||
|
*/ |
||||||
|
public int getFeaturesToLoad() { |
||||||
|
return featuresToLoad; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method creates an object where loading results will be stores. Only those features will be allowed to store |
||||||
|
* that were specified by features-to-load flag. |
||||||
|
* @return an object to store loading results |
||||||
|
*/ |
||||||
|
public LoadingResults prepareLoadingResults() { |
||||||
|
return new LoadingResults(featuresToLoad); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y |
||||||
|
* is up axis. |
||||||
|
* @param fixUpAxis |
||||||
|
* the up axis state variable |
||||||
|
*/ |
||||||
|
public void setFixUpAxis(boolean fixUpAxis) { |
||||||
|
this.fixUpAxis = fixUpAxis; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By |
||||||
|
* default Y is up axis. |
||||||
|
* @return the up axis state variable |
||||||
|
*/ |
||||||
|
public boolean isFixUpAxis() { |
||||||
|
return fixUpAxis; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is |
||||||
|
* not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used |
||||||
|
* during loading (assumin any exists in the file). |
||||||
|
* @param usedWorld |
||||||
|
* the name of the WORLD block used during loading |
||||||
|
*/ |
||||||
|
public void setUsedWorld(String usedWorld) { |
||||||
|
this.usedWorld = usedWorld; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This mehtod returns the name of the WORLD data block taht should be used during file loading. |
||||||
|
* @return the name of the WORLD block used during loading |
||||||
|
*/ |
||||||
|
public String getUsedWorld() { |
||||||
|
return usedWorld; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the default material for objects. |
||||||
|
* @param defaultMaterial |
||||||
|
* the default material |
||||||
|
*/ |
||||||
|
public void setDefaultMaterial(Material defaultMaterial) { |
||||||
|
this.defaultMaterial = defaultMaterial; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the default material. |
||||||
|
* @return the default material |
||||||
|
*/ |
||||||
|
public Material getDefaultMaterial() { |
||||||
|
return defaultMaterial; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void write(JmeExporter e) throws IOException { |
||||||
|
super.write(e); |
||||||
|
OutputCapsule oc = e.getCapsule(this); |
||||||
|
//saving animations
|
||||||
|
oc.write(animations == null ? 0 : animations.size(), "anim-size", 0); |
||||||
|
if(animations != null) { |
||||||
|
int objectCounter = 0; |
||||||
|
for(Entry<String, Map<String, int[]>> animEntry : animations.entrySet()) { |
||||||
|
oc.write(animEntry.getKey(), "animated-object-" + objectCounter, null); |
||||||
|
int animsAmount = animEntry.getValue().size(); |
||||||
|
oc.write(animsAmount, "anims-amount-" + objectCounter, 0); |
||||||
|
for(Entry<String, int[]> animsEntry : animEntry.getValue().entrySet()) { |
||||||
|
oc.write(animsEntry.getKey(), "anim-name-" + objectCounter, null); |
||||||
|
oc.write(animsEntry.getValue(), "anim-frames-" + objectCounter, null); |
||||||
|
} |
||||||
|
++objectCounter; |
||||||
|
} |
||||||
|
} |
||||||
|
//saving the rest of the data
|
||||||
|
oc.write(fps, "fps", DEFAULT_FPS); |
||||||
|
oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL); |
||||||
|
oc.write(assetRootPath, "asset-root-path", null); |
||||||
|
oc.write(fixUpAxis, "fix-up-axis", true); |
||||||
|
oc.write(usedWorld, "used-world", null); |
||||||
|
oc.write(defaultMaterial, "default-material", null); |
||||||
|
oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void read(JmeImporter e) throws IOException { |
||||||
|
super.read(e); |
||||||
|
InputCapsule ic = e.getCapsule(this); |
||||||
|
//reading animations
|
||||||
|
int animSize = ic.readInt("anim-size", 0); |
||||||
|
if(animSize > 0) { |
||||||
|
if(animations == null) { |
||||||
|
animations = new HashMap<String, Map<String, int[]>>(animSize); |
||||||
|
} else { |
||||||
|
animations.clear(); |
||||||
|
} |
||||||
|
for(int i = 0; i < animSize; ++i) { |
||||||
|
String objectName = ic.readString("animated-object-" + i, null); |
||||||
|
int animationsAmount = ic.readInt("anims-amount-" + i, 0); |
||||||
|
Map<String, int[]> objectAnimations = new HashMap<String, int[]>(animationsAmount); |
||||||
|
for(int j = 0; j < animationsAmount; ++j) { |
||||||
|
String animName = ic.readString("anim-name-" + i, null); |
||||||
|
int[] animFrames = ic.readIntArray("anim-frames-" + i, null); |
||||||
|
objectAnimations.put(animName, animFrames); |
||||||
|
} |
||||||
|
animations.put(objectName, objectAnimations); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//reading the rest of the data
|
||||||
|
fps = ic.readInt("fps", DEFAULT_FPS); |
||||||
|
featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL); |
||||||
|
assetRootPath = ic.readString("asset-root-path", null); |
||||||
|
fixUpAxis = ic.readBoolean("fix-up-axis", true); |
||||||
|
usedWorld = ic.readString("used-world", null); |
||||||
|
defaultMaterial = (Material)ic.readSavable("default-material", null); |
||||||
|
faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
final int prime = 31; |
||||||
|
int result = super.hashCode(); |
||||||
|
result = prime * result + (animations == null ? 0 : animations.hashCode()); |
||||||
|
result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode()); |
||||||
|
result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode()); |
||||||
|
result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode()); |
||||||
|
result = prime * result + featuresToLoad; |
||||||
|
result = prime * result + (fixUpAxis ? 1231 : 1237); |
||||||
|
result = prime * result + fps; |
||||||
|
result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if(this == obj) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if(!super.equals(obj)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(this.getClass() != obj.getClass()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
BlenderKey other = (BlenderKey)obj; |
||||||
|
if(animations == null) { |
||||||
|
if(other.animations != null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} else if(!animations.equals(other.animations)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(assetRootPath == null) { |
||||||
|
if(other.assetRootPath != null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} else if(!assetRootPath.equals(other.assetRootPath)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(defaultMaterial == null) { |
||||||
|
if(other.defaultMaterial != null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} else if(!defaultMaterial.equals(other.defaultMaterial)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(faceCullMode != other.faceCullMode) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(featuresToLoad != other.featuresToLoad) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(fixUpAxis != other.fixUpAxis) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(fps != other.fps) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(usedWorld == null) { |
||||||
|
if(other.usedWorld != null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} else if(!usedWorld.equals(other.usedWorld)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This interface describes the features of the scene that are to be loaded. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public static interface FeaturesToLoad { |
||||||
|
int SCENES = 0x0000FFFF; |
||||||
|
int OBJECTS = 0x0000000B; |
||||||
|
int ANIMATIONS = 0x00000004; |
||||||
|
int MATERIALS = 0x00000003; |
||||||
|
int TEXTURES = 0x00000001; |
||||||
|
int CAMERAS = 0x00000020; |
||||||
|
int LIGHTS = 0x00000010; |
||||||
|
int ALL = 0xFFFFFFFF; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This class holds the loading results according to the given loading flag. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public static class LoadingResults extends Spatial { |
||||||
|
/** Bitwise mask of features that are to be loaded. */ |
||||||
|
private final int featuresToLoad; |
||||||
|
/** The scenes from the file. */ |
||||||
|
private List<Node> scenes; |
||||||
|
/** Objects from all scenes. */ |
||||||
|
private List<Node> objects; |
||||||
|
/** Materials from all objects. */ |
||||||
|
private List<Material> materials; |
||||||
|
/** Textures from all objects. */ |
||||||
|
private List<Texture> textures; |
||||||
|
/** Animations of all objects. */ |
||||||
|
private List<AnimData> animations; |
||||||
|
/** All cameras from the file. */ |
||||||
|
private List<Camera> cameras; |
||||||
|
/** All lights from the file. */ |
||||||
|
private List<Light> lights; |
||||||
|
|
||||||
|
/** |
||||||
|
* Private constructor prevents users to create an instance of this class from outside the |
||||||
|
* @param featuresToLoad |
||||||
|
* bitwise mask of features that are to be loaded |
||||||
|
* @see FeaturesToLoad FeaturesToLoad |
||||||
|
*/ |
||||||
|
private LoadingResults(int featuresToLoad) { |
||||||
|
this.featuresToLoad = featuresToLoad; |
||||||
|
if((featuresToLoad & FeaturesToLoad.SCENES) != 0) { |
||||||
|
scenes = new ArrayList<Node>(); |
||||||
|
} |
||||||
|
if((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) { |
||||||
|
objects = new ArrayList<Node>(); |
||||||
|
if((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) { |
||||||
|
materials = new ArrayList<Material>(); |
||||||
|
if((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) { |
||||||
|
textures = new ArrayList<Texture>(); |
||||||
|
} |
||||||
|
} |
||||||
|
if((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) { |
||||||
|
animations = new ArrayList<AnimData>(); |
||||||
|
} |
||||||
|
} |
||||||
|
if((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) { |
||||||
|
cameras = new ArrayList<Camera>(); |
||||||
|
} |
||||||
|
if((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) { |
||||||
|
lights = new ArrayList<Light>(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a bitwise flag describing what features of the blend file will be included in the result. |
||||||
|
* @return bitwise mask of features that are to be loaded |
||||||
|
* @see FeaturesToLoad FeaturesToLoad |
||||||
|
*/ |
||||||
|
public int getLoadedFeatures() { |
||||||
|
return featuresToLoad; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a scene to the result set. |
||||||
|
* @param scene |
||||||
|
* scene to be added to the result set |
||||||
|
*/ |
||||||
|
public void addScene(Node scene) { |
||||||
|
if(scenes != null) { |
||||||
|
scenes.add(scene); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds an object to the result set. |
||||||
|
* @param object |
||||||
|
* object to be added to the result set |
||||||
|
*/ |
||||||
|
public void addObject(Node object) { |
||||||
|
if(objects != null) { |
||||||
|
objects.add(object); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a material to the result set. |
||||||
|
* @param material |
||||||
|
* material to be added to the result set |
||||||
|
*/ |
||||||
|
public void addMaterial(Material material) { |
||||||
|
if(materials != null) { |
||||||
|
materials.add(material); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a texture to the result set. |
||||||
|
* @param texture |
||||||
|
* texture to be added to the result set |
||||||
|
*/ |
||||||
|
public void addTexture(Texture texture) { |
||||||
|
if(textures != null) { |
||||||
|
textures.add(texture); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a camera to the result set. |
||||||
|
* @param camera |
||||||
|
* camera to be added to the result set |
||||||
|
*/ |
||||||
|
public void addCamera(Camera camera) { |
||||||
|
if(cameras != null) { |
||||||
|
cameras.add(camera); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a light to the result set. |
||||||
|
* @param light |
||||||
|
* light to be added to the result set |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void addLight(Light light) { |
||||||
|
if(lights != null) { |
||||||
|
lights.add(light); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns all loaded scenes. |
||||||
|
* @return all loaded scenes |
||||||
|
*/ |
||||||
|
public List<Node> getScenes() { |
||||||
|
return scenes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns all loaded objects. |
||||||
|
* @return all loaded objects |
||||||
|
*/ |
||||||
|
public List<Node> getObjects() { |
||||||
|
return objects; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns all loaded materials. |
||||||
|
* @return all loaded materials |
||||||
|
*/ |
||||||
|
public List<Material> getMaterials() { |
||||||
|
return materials; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns all loaded textures. |
||||||
|
* @return all loaded textures |
||||||
|
*/ |
||||||
|
public List<Texture> getTextures() { |
||||||
|
return textures; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns all loaded animations. |
||||||
|
* @return all loaded animations |
||||||
|
*/ |
||||||
|
public List<AnimData> getAnimations() { |
||||||
|
return animations; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns all loaded cameras. |
||||||
|
* @return all loaded cameras |
||||||
|
*/ |
||||||
|
public List<Camera> getCameras() { |
||||||
|
return cameras; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns all loaded lights. |
||||||
|
* @return all loaded lights |
||||||
|
*/ |
||||||
|
public List<Light> getLights() { |
||||||
|
return lights; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void updateModelBound() {} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setModelBound(BoundingVolume modelBound) {} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getVertexCount() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getTriangleCount() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Spatial deepClone() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void depthFirstTraversal(SceneGraphVisitor visitor) {} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient |
||||||
|
* light. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public static class WorldData { |
||||||
|
/** The ambient light. */ |
||||||
|
private AmbientLight ambientLight; |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the world's ambient light. |
||||||
|
* @return the world's ambient light |
||||||
|
*/ |
||||||
|
public AmbientLight getAmbientLight() { |
||||||
|
return ambientLight; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the world's ambient light. |
||||||
|
* @param ambientLight |
||||||
|
* the world's ambient light |
||||||
|
*/ |
||||||
|
public void setAmbientLight(AmbientLight ambientLight) { |
||||||
|
this.ambientLight = ambientLight; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,193 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetInfo; |
||||||
|
import com.jme3.asset.AssetLoader; |
||||||
|
import com.jme3.asset.BlenderKey; |
||||||
|
import com.jme3.asset.BlenderKey.FeaturesToLoad; |
||||||
|
import com.jme3.asset.BlenderKey.LoadingResults; |
||||||
|
import com.jme3.asset.BlenderKey.WorldData; |
||||||
|
import com.jme3.asset.ModelKey; |
||||||
|
import com.jme3.light.Light; |
||||||
|
import com.jme3.renderer.Camera; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.plugins.blender.data.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ArmatureHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.CameraHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ConstraintHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.CurvesHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.IpoHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.LightHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.MaterialHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.MeshHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ModifierHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.NoiseHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ObjectHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ParticlesHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.TextureHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.BlenderInputStream; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.JmeConverter; |
||||||
|
|
||||||
|
/** |
||||||
|
* This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class BlenderLoader implements AssetLoader { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName()); |
||||||
|
|
||||||
|
@Override |
||||||
|
public LoadingResults load(AssetInfo assetInfo) throws IOException { |
||||||
|
try { |
||||||
|
//registering loaders
|
||||||
|
ModelKey modelKey = (ModelKey)assetInfo.getKey(); |
||||||
|
BlenderKey blenderKey; |
||||||
|
if(modelKey instanceof BlenderKey) { |
||||||
|
blenderKey = (BlenderKey)modelKey; |
||||||
|
} else { |
||||||
|
blenderKey = new BlenderKey(modelKey.getName()); |
||||||
|
blenderKey.setAssetRootPath(modelKey.getFolder()); |
||||||
|
} |
||||||
|
|
||||||
|
//opening stream
|
||||||
|
BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream(), assetInfo.getManager()); |
||||||
|
|
||||||
|
//reading blocks
|
||||||
|
List<FileBlockHeader> blocks = new ArrayList<FileBlockHeader>(); |
||||||
|
FileBlockHeader fileBlock; |
||||||
|
DataRepository dataRepository = new DataRepository(); |
||||||
|
dataRepository.setAssetManager(assetInfo.getManager()); |
||||||
|
dataRepository.setInputStream(inputStream); |
||||||
|
dataRepository.setBlenderKey(blenderKey); |
||||||
|
|
||||||
|
//creating helpers
|
||||||
|
dataRepository.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), dataRepository)); |
||||||
|
dataRepository.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(NoiseHelper.class, new NoiseHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber())); |
||||||
|
|
||||||
|
//setting additional data to helpers
|
||||||
|
if(blenderKey.isFixUpAxis()) { |
||||||
|
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); |
||||||
|
objectHelper.setyIsUpAxis(true); |
||||||
|
CurvesHelper curvesHelper = dataRepository.getHelper(CurvesHelper.class); |
||||||
|
curvesHelper.setyIsUpAxis(true); |
||||||
|
} |
||||||
|
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); |
||||||
|
materialHelper.setFaceCullMode(blenderKey.getFaceCullMode()); |
||||||
|
|
||||||
|
//reading the blocks (dna block is automatically saved in the data repository when found)//TODO: zmienić to
|
||||||
|
do { |
||||||
|
fileBlock = new FileBlockHeader(inputStream, dataRepository); |
||||||
|
if(!fileBlock.isDnaBlock()) { |
||||||
|
blocks.add(fileBlock); |
||||||
|
} |
||||||
|
} while(!fileBlock.isLastBlock()); |
||||||
|
|
||||||
|
JmeConverter converter = new JmeConverter(dataRepository); |
||||||
|
LoadingResults loadingResults = blenderKey.prepareLoadingResults(); |
||||||
|
WorldData worldData = null;//a set of data used in different scene aspects
|
||||||
|
for(FileBlockHeader block : blocks) { |
||||||
|
switch(block.getCode()) { |
||||||
|
case FileBlockHeader.BLOCK_OB00://Object
|
||||||
|
Object object = converter.toObject(block.getStructure(dataRepository)); |
||||||
|
if(object instanceof Node) { |
||||||
|
if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { |
||||||
|
LOGGER.log(Level.INFO, ((Node)object).getName() + ": " + ((Node)object).getLocalTranslation().toString() + "--> " + (((Node)object).getParent() == null ? "null" : ((Node)object).getParent().getName())); |
||||||
|
if(((Node)object).getParent() == null) { |
||||||
|
loadingResults.addObject((Node)object); |
||||||
|
} |
||||||
|
} |
||||||
|
} else if(object instanceof Camera) { |
||||||
|
if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0) { |
||||||
|
loadingResults.addCamera((Camera)object); |
||||||
|
} |
||||||
|
} else if(object instanceof Light) { |
||||||
|
if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { |
||||||
|
loadingResults.addLight((Light)object); |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
case FileBlockHeader.BLOCK_MA00://Material
|
||||||
|
if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { |
||||||
|
loadingResults.addMaterial(converter.toMaterial(block.getStructure(dataRepository))); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FileBlockHeader.BLOCK_SC00://Scene
|
||||||
|
if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) { |
||||||
|
loadingResults.addScene(converter.toScene(block.getStructure(dataRepository))); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FileBlockHeader.BLOCK_WO00://World
|
||||||
|
if(worldData == null) {//onlu one world data is used
|
||||||
|
Structure worldStructure = block.getStructure(dataRepository); |
||||||
|
String worldName = worldStructure.getName(); |
||||||
|
if(blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { |
||||||
|
worldData = converter.toWorldData(worldStructure); |
||||||
|
if((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { |
||||||
|
loadingResults.addLight(worldData.getAmbientLight()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
try { |
||||||
|
inputStream.close(); |
||||||
|
} catch(IOException e) { |
||||||
|
LOGGER.log(Level.SEVERE, e.getMessage(), e); |
||||||
|
} |
||||||
|
return loadingResults; |
||||||
|
} catch(BlenderFileException e) { |
||||||
|
LOGGER.log(Level.SEVERE, e.getMessage(), e); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetInfo; |
||||||
|
import com.jme3.asset.AssetLoader; |
||||||
|
import com.jme3.asset.BlenderKey; |
||||||
|
import com.jme3.asset.BlenderKey.LoadingResults; |
||||||
|
import com.jme3.asset.ModelKey; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.plugins.blender.data.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ArmatureHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.CameraHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ConstraintHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.CurvesHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.IpoHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.LightHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.MaterialHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.MeshHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ModifierHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.NoiseHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ObjectHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ParticlesHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.TextureHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.BlenderInputStream; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.JmeConverter; |
||||||
|
|
||||||
|
/** |
||||||
|
* This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class BlenderModelLoader implements AssetLoader { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(BlenderModelLoader.class.getName()); |
||||||
|
|
||||||
|
@Override |
||||||
|
public Spatial load(AssetInfo assetInfo) throws IOException { |
||||||
|
try { |
||||||
|
//registering loaders
|
||||||
|
ModelKey modelKey = (ModelKey)assetInfo.getKey(); |
||||||
|
BlenderKey blenderKey; |
||||||
|
if(modelKey instanceof BlenderKey) { |
||||||
|
blenderKey = (BlenderKey)modelKey; |
||||||
|
} else { |
||||||
|
blenderKey = new BlenderKey(modelKey.getName()); |
||||||
|
blenderKey.setAssetRootPath(modelKey.getFolder()); |
||||||
|
} |
||||||
|
|
||||||
|
//opening stream
|
||||||
|
BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream(), assetInfo.getManager()); |
||||||
|
List<FileBlockHeader> blocks = new ArrayList<FileBlockHeader>(); |
||||||
|
FileBlockHeader fileBlock; |
||||||
|
DataRepository dataRepository = new DataRepository(); |
||||||
|
dataRepository.setAssetManager(assetInfo.getManager()); |
||||||
|
dataRepository.setInputStream(inputStream); |
||||||
|
dataRepository.setBlenderKey(blenderKey); |
||||||
|
dataRepository.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), dataRepository)); |
||||||
|
dataRepository.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(NoiseHelper.class, new NoiseHelper(inputStream.getVersionNumber())); |
||||||
|
dataRepository.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber())); |
||||||
|
|
||||||
|
//setting additional data to helpers
|
||||||
|
if(blenderKey.isFixUpAxis()) { |
||||||
|
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); |
||||||
|
objectHelper.setyIsUpAxis(true); |
||||||
|
CurvesHelper curvesHelper = dataRepository.getHelper(CurvesHelper.class); |
||||||
|
curvesHelper.setyIsUpAxis(true); |
||||||
|
} |
||||||
|
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); |
||||||
|
materialHelper.setFaceCullMode(blenderKey.getFaceCullMode()); |
||||||
|
|
||||||
|
//reading the blocks (dna block is automatically saved in the data repository when found)//TODO: zmienić to
|
||||||
|
do { |
||||||
|
fileBlock = new FileBlockHeader(inputStream, dataRepository); |
||||||
|
if(!fileBlock.isDnaBlock()) { |
||||||
|
blocks.add(fileBlock); |
||||||
|
} |
||||||
|
} while(!fileBlock.isLastBlock()); |
||||||
|
|
||||||
|
JmeConverter converter = new JmeConverter(dataRepository); |
||||||
|
LoadingResults loadingResults = blenderKey.prepareLoadingResults(); |
||||||
|
for(FileBlockHeader block : blocks) { |
||||||
|
if(block.getCode() == FileBlockHeader.BLOCK_OB00) { |
||||||
|
Object object = converter.toObject(block.getStructure(dataRepository)); |
||||||
|
if(object instanceof Node) { |
||||||
|
LOGGER.log(Level.INFO, ((Node)object).getName() + ": " + ((Node)object).getLocalTranslation().toString() + "--> " + (((Node)object).getParent() == null ? "null" : ((Node)object).getParent().getName())); |
||||||
|
if(((Node)object).getParent() == null) { |
||||||
|
loadingResults.addObject((Node)object); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
inputStream.close(); |
||||||
|
List<Node> objects = loadingResults.getObjects(); |
||||||
|
if(objects.size() > 0) { |
||||||
|
Node modelNode = new Node(blenderKey.getName()); |
||||||
|
for(Iterator<Node> it = objects.iterator(); it.hasNext();) { |
||||||
|
Node node = it.next(); |
||||||
|
modelNode.attachChild(node); |
||||||
|
} |
||||||
|
return modelNode; |
||||||
|
} else if(objects.size() == 1) { |
||||||
|
return objects.get(0); |
||||||
|
} |
||||||
|
} catch(BlenderFileException e) { |
||||||
|
LOGGER.log(Level.SEVERE, e.getMessage(), e); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,210 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.data; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.utils.BlenderInputStream; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
|
||||||
|
/** |
||||||
|
* The data block containing the description of the file. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class DnaBlockData { |
||||||
|
private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; //SDNA
|
||||||
|
private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; //NAME
|
||||||
|
private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; //TYPE
|
||||||
|
private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; //TLEN
|
||||||
|
private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; //STRC
|
||||||
|
|
||||||
|
/** Structures available inside the file. */ |
||||||
|
private final Structure[] structures; |
||||||
|
/** A map that helps finding a structure by type. */ |
||||||
|
private final Map<String, Structure> structuresMap; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Loads the block from the given stream during instance creation. |
||||||
|
* @param inputStream |
||||||
|
* the stream we read the block from |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is throw if the blend file is invalid or somehow corrupted |
||||||
|
*/ |
||||||
|
public DnaBlockData(BlenderInputStream inputStream, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
int identifier; |
||||||
|
|
||||||
|
//reading 'SDNA' identifier
|
||||||
|
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | |
||||||
|
inputStream.readByte() << 8 | inputStream.readByte(); |
||||||
|
|
||||||
|
if(identifier != SDNA_ID) { |
||||||
|
throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier)); |
||||||
|
} |
||||||
|
|
||||||
|
//reading names
|
||||||
|
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | |
||||||
|
inputStream.readByte() << 8 | inputStream.readByte(); |
||||||
|
if(identifier != NAME_ID) { |
||||||
|
throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier)); |
||||||
|
} |
||||||
|
int amount = inputStream.readInt(); |
||||||
|
if(amount <= 0) { |
||||||
|
throw new BlenderFileException("The names amount number should be positive!"); |
||||||
|
} |
||||||
|
String[] names = new String[amount]; |
||||||
|
for(int i = 0; i < amount; ++i) { |
||||||
|
names[i] = inputStream.readString(); |
||||||
|
} |
||||||
|
|
||||||
|
//reding types
|
||||||
|
inputStream.alignPosition(4); |
||||||
|
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | |
||||||
|
inputStream.readByte() << 8 | inputStream.readByte(); |
||||||
|
if(identifier != TYPE_ID) { |
||||||
|
throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier)); |
||||||
|
} |
||||||
|
amount = inputStream.readInt(); |
||||||
|
if(amount <= 0) { |
||||||
|
throw new BlenderFileException("The types amount number should be positive!"); |
||||||
|
} |
||||||
|
String[] types = new String[amount]; |
||||||
|
for(int i = 0; i < amount; ++i) { |
||||||
|
types[i] = inputStream.readString(); |
||||||
|
} |
||||||
|
|
||||||
|
//reading lengths
|
||||||
|
inputStream.alignPosition(4); |
||||||
|
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | |
||||||
|
inputStream.readByte() << 8 | inputStream.readByte(); |
||||||
|
if(identifier != TLEN_ID) { |
||||||
|
throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier)); |
||||||
|
} |
||||||
|
int[] lengths = new int[amount];//theamount is the same as int types
|
||||||
|
for(int i = 0; i < amount; ++i) { |
||||||
|
lengths[i] = inputStream.readShort(); |
||||||
|
} |
||||||
|
|
||||||
|
//reading structures
|
||||||
|
inputStream.alignPosition(4); |
||||||
|
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | |
||||||
|
inputStream.readByte() << 8 | inputStream.readByte(); |
||||||
|
if(identifier != STRC_ID) { |
||||||
|
throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier)); |
||||||
|
} |
||||||
|
amount = inputStream.readInt(); |
||||||
|
if(amount <= 0) { |
||||||
|
throw new BlenderFileException("The structures amount number should be positive!"); |
||||||
|
} |
||||||
|
structures = new Structure[amount]; |
||||||
|
structuresMap = new HashMap<String, Structure>(amount); |
||||||
|
for(int i = 0; i < amount; ++i) { |
||||||
|
structures[i] = new Structure(inputStream, names, types, dataRepository); |
||||||
|
if(structuresMap.containsKey(structures[i].getType())) { |
||||||
|
throw new BlenderFileException("Blend file seems to be corrupted! The type " + structures[i].getType() + " is defined twice!"); |
||||||
|
} |
||||||
|
structuresMap.put(structures[i].getType(), structures[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the amount of the structures. |
||||||
|
* @return the amount of the structures |
||||||
|
*/ |
||||||
|
public int getStructuresCount() { |
||||||
|
return structures.length; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the structure of the given index. |
||||||
|
* @param index |
||||||
|
* the index of the structure |
||||||
|
* @return the structure of the given index |
||||||
|
*/ |
||||||
|
public Structure getStructure(int index) { |
||||||
|
try { |
||||||
|
return (Structure)structures[index].clone(); |
||||||
|
} catch(CloneNotSupportedException e) { |
||||||
|
throw new IllegalStateException("Structure should be clonable!!!", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a structure of the given name. If the name does not exists then null is returned. |
||||||
|
* @param name |
||||||
|
* the name of the structure |
||||||
|
* @return the required structure or null if the given name is inapropriate |
||||||
|
*/ |
||||||
|
public Structure getStructure(String name) { |
||||||
|
try { |
||||||
|
return (Structure)structuresMap.get(name).clone(); |
||||||
|
} catch(CloneNotSupportedException e) { |
||||||
|
throw new IllegalStateException(e.getMessage(), e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method indicates if the structure of the given name exists. |
||||||
|
* @param name |
||||||
|
* the name of the structure |
||||||
|
* @return true if the structure exists and false otherwise |
||||||
|
*/ |
||||||
|
public boolean hasStructure(String name) { |
||||||
|
return structuresMap.containsKey(name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method converts the given identifier code to string. |
||||||
|
* @param code |
||||||
|
* the code taht is to be converted |
||||||
|
* @return the string value of the identifier |
||||||
|
*/ |
||||||
|
private String toString(int code) { |
||||||
|
char c1 = (char)((code & 0xFF000000) >> 24); |
||||||
|
char c2 = (char)((code & 0xFF0000) >> 16); |
||||||
|
char c3 = (char)((code & 0xFF00) >> 8); |
||||||
|
char c4 = (char)(code & 0xFF); |
||||||
|
return String.valueOf(c1) + c2 + c3 + c4; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
StringBuilder stringBuilder = new StringBuilder("=============== ").append(SDNA_ID).append('\n'); |
||||||
|
for(Structure structure : structures) { |
||||||
|
stringBuilder.append(structure.toString()).append('\n'); |
||||||
|
} |
||||||
|
return stringBuilder.append("===============").toString(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,319 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.data; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.blender.data.Structure.DataType; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.utils.BlenderInputStream; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class represents a single field in the structure. It can be either a primitive type or a table or a reference to |
||||||
|
* another structure. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
/*package*/class Field implements Cloneable { |
||||||
|
private static final int NAME_LENGTH = 24; |
||||||
|
private static final int TYPE_LENGTH = 16; |
||||||
|
|
||||||
|
/** The data repository. */ |
||||||
|
public DataRepository dataRepository; |
||||||
|
/** The type of the field. */ |
||||||
|
public String type; |
||||||
|
/** The name of the field. */ |
||||||
|
public String name; |
||||||
|
/** The value of the field. Filled during data reading. */ |
||||||
|
public Object value; |
||||||
|
/** This variable indicates the level of the pointer. */ |
||||||
|
public int pointerLevel; |
||||||
|
/** |
||||||
|
* This variable determines the sizes of the array. If the value is null the n the field is not an array. |
||||||
|
*/ |
||||||
|
public int[] tableSizes; |
||||||
|
/** This variable indicates if the field is a function pointer. */ |
||||||
|
public boolean function; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Saves the field data and parses its name. |
||||||
|
* @param name |
||||||
|
* the name of the field |
||||||
|
* @param type |
||||||
|
* the type of the field |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown if the names contain errors |
||||||
|
*/ |
||||||
|
public Field(String name, String type, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
this.type = type; |
||||||
|
this.dataRepository = dataRepository; |
||||||
|
this.parseField(new StringBuilder(name)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we |
||||||
|
* have a clead empty copy of the filed to fill with data. |
||||||
|
* @param field |
||||||
|
* the object that we copy |
||||||
|
*/ |
||||||
|
private Field(Field field) { |
||||||
|
type = field.type; |
||||||
|
name = field.name; |
||||||
|
dataRepository = field.dataRepository; |
||||||
|
pointerLevel = field.pointerLevel; |
||||||
|
if(field.tableSizes != null) { |
||||||
|
tableSizes = field.tableSizes.clone(); |
||||||
|
} |
||||||
|
function = field.function; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object clone() throws CloneNotSupportedException { |
||||||
|
return new Field(this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method fills the field wth data read from the input stream. |
||||||
|
* @param blenderInputStream |
||||||
|
* the stream we read data from |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when the blend file is somehow invalid or corrupted |
||||||
|
*/ |
||||||
|
public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException { |
||||||
|
int dataToRead = 1; |
||||||
|
if(tableSizes != null && tableSizes.length > 0) { |
||||||
|
for(int size : tableSizes) { |
||||||
|
if(size <= 0) { |
||||||
|
throw new BlenderFileException("The field " + name + " has invalid table size: " + size); |
||||||
|
} |
||||||
|
dataToRead *= size; |
||||||
|
} |
||||||
|
} |
||||||
|
DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, dataRepository) : DataType.POINTER; |
||||||
|
switch(dataType) { |
||||||
|
case POINTER: |
||||||
|
if(dataToRead == 1) { |
||||||
|
Pointer pointer = new Pointer(pointerLevel, function, dataRepository); |
||||||
|
pointer.fill(blenderInputStream); |
||||||
|
value = pointer; |
||||||
|
} else { |
||||||
|
Pointer[] data = new Pointer[dataToRead]; |
||||||
|
for(int i = 0; i < dataToRead; ++i) { |
||||||
|
Pointer pointer = new Pointer(pointerLevel, function, dataRepository); |
||||||
|
pointer.fill(blenderInputStream); |
||||||
|
data[i] = pointer; |
||||||
|
} |
||||||
|
value = new DynamicArray<Pointer>(tableSizes, data); |
||||||
|
} |
||||||
|
break; |
||||||
|
case CHARACTER: |
||||||
|
//character is also stored as a number, because sometimes the new blender version uses
|
||||||
|
//other number type instead of character as a field type
|
||||||
|
//and characters are very often used as byte number stores instead of real chars
|
||||||
|
if(dataToRead == 1) { |
||||||
|
value = Byte.valueOf((byte)blenderInputStream.readByte()); |
||||||
|
} else { |
||||||
|
Character[] data = new Character[dataToRead]; |
||||||
|
for(int i = 0; i < dataToRead; ++i) { |
||||||
|
data[i] = Character.valueOf((char)blenderInputStream.readByte()); |
||||||
|
} |
||||||
|
value = new DynamicArray<Character>(tableSizes, data); |
||||||
|
} |
||||||
|
break; |
||||||
|
case SHORT: |
||||||
|
if(dataToRead == 1) { |
||||||
|
value = Integer.valueOf(blenderInputStream.readShort()); |
||||||
|
} else { |
||||||
|
Number[] data = new Number[dataToRead]; |
||||||
|
for(int i = 0; i < dataToRead; ++i) { |
||||||
|
data[i] = Integer.valueOf(blenderInputStream.readShort()); |
||||||
|
} |
||||||
|
value = new DynamicArray<Number>(tableSizes, data); |
||||||
|
} |
||||||
|
break; |
||||||
|
case INTEGER: |
||||||
|
if(dataToRead == 1) { |
||||||
|
value = Integer.valueOf(blenderInputStream.readInt()); |
||||||
|
} else { |
||||||
|
Number[] data = new Number[dataToRead]; |
||||||
|
for(int i = 0; i < dataToRead; ++i) { |
||||||
|
data[i] = Integer.valueOf(blenderInputStream.readInt()); |
||||||
|
} |
||||||
|
value = new DynamicArray<Number>(tableSizes, data); |
||||||
|
} |
||||||
|
break; |
||||||
|
case LONG: |
||||||
|
if(dataToRead == 1) { |
||||||
|
value = Long.valueOf(blenderInputStream.readLong()); |
||||||
|
} else { |
||||||
|
Number[] data = new Number[dataToRead]; |
||||||
|
for(int i = 0; i < dataToRead; ++i) { |
||||||
|
data[i] = Long.valueOf(blenderInputStream.readLong()); |
||||||
|
} |
||||||
|
value = new DynamicArray<Number>(tableSizes, data); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FLOAT: |
||||||
|
if(dataToRead == 1) { |
||||||
|
value = Float.valueOf(blenderInputStream.readFloat()); |
||||||
|
} else { |
||||||
|
Number[] data = new Number[dataToRead]; |
||||||
|
for(int i = 0; i < dataToRead; ++i) { |
||||||
|
data[i] = Float.valueOf(blenderInputStream.readFloat()); |
||||||
|
} |
||||||
|
value = new DynamicArray<Number>(tableSizes, data); |
||||||
|
} |
||||||
|
break; |
||||||
|
case DOUBLE: |
||||||
|
if(dataToRead == 1) { |
||||||
|
value = Double.valueOf(blenderInputStream.readDouble()); |
||||||
|
} else { |
||||||
|
Number[] data = new Number[dataToRead]; |
||||||
|
for(int i = 0; i < dataToRead; ++i) { |
||||||
|
data[i] = Double.valueOf(blenderInputStream.readDouble()); |
||||||
|
} |
||||||
|
value = new DynamicArray<Number>(tableSizes, data); |
||||||
|
} |
||||||
|
break; |
||||||
|
case VOID: |
||||||
|
break; |
||||||
|
case STRUCTURE: |
||||||
|
if(dataToRead == 1) { |
||||||
|
Structure structure = dataRepository.getDnaBlockData().getStructure(type); |
||||||
|
structure.fill(blenderInputStream); |
||||||
|
value = structure; |
||||||
|
} else { |
||||||
|
Structure[] data = new Structure[dataToRead]; |
||||||
|
for(int i = 0; i < dataToRead; ++i) { |
||||||
|
Structure structure = dataRepository.getDnaBlockData().getStructure(type); |
||||||
|
structure.fill(blenderInputStream); |
||||||
|
data[i] = structure; |
||||||
|
} |
||||||
|
value = new DynamicArray<Structure>(tableSizes, data); |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new IllegalStateException("Unimplemented filling of type: " + type); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method parses the field name to determine how the field should be used. |
||||||
|
* @param nameBuilder |
||||||
|
* the name of the field (given as StringBuilder) |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown if the names contain errors |
||||||
|
*/ |
||||||
|
private void parseField(StringBuilder nameBuilder) throws BlenderFileException { |
||||||
|
this.removeWhitespaces(nameBuilder); |
||||||
|
//veryfying if the name is a pointer
|
||||||
|
int pointerIndex = nameBuilder.indexOf("*"); |
||||||
|
while(pointerIndex >= 0) { |
||||||
|
++pointerLevel; |
||||||
|
nameBuilder.deleteCharAt(pointerIndex); |
||||||
|
pointerIndex = nameBuilder.indexOf("*"); |
||||||
|
} |
||||||
|
//veryfying if the name is a function pointer
|
||||||
|
if(nameBuilder.indexOf("(") >= 0) { |
||||||
|
function = true; |
||||||
|
this.removeCharacter(nameBuilder, '('); |
||||||
|
this.removeCharacter(nameBuilder, ')'); |
||||||
|
} else { |
||||||
|
//veryfying if the name is a table
|
||||||
|
int tableStartIndex = 0; |
||||||
|
List<Integer> lengths = new ArrayList<Integer>(3);//3 dimensions will be enough in most cases
|
||||||
|
do { |
||||||
|
tableStartIndex = nameBuilder.indexOf("["); |
||||||
|
if(tableStartIndex > 0) { |
||||||
|
int tableStopIndex = nameBuilder.indexOf("]"); |
||||||
|
if(tableStopIndex < 0) { |
||||||
|
throw new BlenderFileException("Invalid structure name: " + name); |
||||||
|
} |
||||||
|
try { |
||||||
|
lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex))); |
||||||
|
} catch(NumberFormatException e) { |
||||||
|
throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e); |
||||||
|
} |
||||||
|
nameBuilder.delete(tableStartIndex, tableStopIndex + 1); |
||||||
|
} |
||||||
|
} while(tableStartIndex > 0); |
||||||
|
if(!lengths.isEmpty()) { |
||||||
|
tableSizes = new int[lengths.size()]; |
||||||
|
for(int i = 0; i < tableSizes.length; ++i) { |
||||||
|
tableSizes[i] = lengths.get(i).intValue(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
name = nameBuilder.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method removes the required character from the text. |
||||||
|
* @param text |
||||||
|
* the text we remove characters from |
||||||
|
* @param toRemove |
||||||
|
* the character to be removed |
||||||
|
*/ |
||||||
|
private void removeCharacter(StringBuilder text, char toRemove) { |
||||||
|
for(int i = 0; i < text.length(); ++i) { |
||||||
|
if(text.charAt(i) == toRemove) { |
||||||
|
text.deleteCharAt(i); |
||||||
|
--i; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method removes all whitespaces from the text. |
||||||
|
* @param text |
||||||
|
* the text we remove whitespaces from |
||||||
|
*/ |
||||||
|
private void removeWhitespaces(StringBuilder text) { |
||||||
|
for(int i = 0; i < text.length(); ++i) { |
||||||
|
if(Character.isWhitespace(text.charAt(i))) { |
||||||
|
text.deleteCharAt(i); |
||||||
|
--i; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
if(function) { |
||||||
|
result.append('('); |
||||||
|
} |
||||||
|
for(int i = 0; i < pointerLevel; ++i) { |
||||||
|
result.append('*'); |
||||||
|
} |
||||||
|
result.append(name); |
||||||
|
if(tableSizes != null) { |
||||||
|
for(int i = 0; i < tableSizes.length; ++i) { |
||||||
|
result.append('[').append(tableSizes[i]).append(']'); |
||||||
|
} |
||||||
|
} |
||||||
|
if(function) { |
||||||
|
result.append(")()"); |
||||||
|
} |
||||||
|
//insert appropriate amount of spaces to format the output corrently
|
||||||
|
int nameLength = result.length(); |
||||||
|
result.append(' ');//at least one space is a must
|
||||||
|
for(int i = 1; i < NAME_LENGTH - nameLength; ++i) {//we start from i=1 because one space is already added
|
||||||
|
result.append(' '); |
||||||
|
} |
||||||
|
result.append(type); |
||||||
|
nameLength = result.length(); |
||||||
|
for(int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) { |
||||||
|
result.append(' '); |
||||||
|
} |
||||||
|
if(value instanceof Character) { |
||||||
|
result.append(" = ").append((int)((Character)value).charValue()); |
||||||
|
} else { |
||||||
|
result.append(" = ").append(value != null ? value.toString() : "null"); |
||||||
|
} |
||||||
|
return result.toString(); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,311 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.data; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.LinkedList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.utils.BlenderInputStream; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class representing a single structure in the file. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class Structure implements Cloneable { |
||||||
|
/** The data repository. */ |
||||||
|
private DataRepository dataRepository; |
||||||
|
/** The address of the block that fills the structure. */ |
||||||
|
private transient Long oldMemoryAddress; |
||||||
|
/** The type of the structure. */ |
||||||
|
private String type; |
||||||
|
/** |
||||||
|
* The fields of the structure. Each field consists of a pair: name-type. |
||||||
|
*/ |
||||||
|
private Field[] fields; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor that copies the data of the structure. |
||||||
|
* @param structure |
||||||
|
* the structure to copy. |
||||||
|
* @param dataRepository |
||||||
|
* the data repository of the structure |
||||||
|
* @throws CloneNotSupportedException |
||||||
|
* this exception should never be thrown |
||||||
|
*/ |
||||||
|
private Structure(Structure structure, DataRepository dataRepository) throws CloneNotSupportedException { |
||||||
|
type = structure.type; |
||||||
|
fields = new Field[structure.fields.length]; |
||||||
|
for(int i = 0; i < fields.length; ++i) { |
||||||
|
fields[i] = (Field)structure.fields[i].clone(); |
||||||
|
} |
||||||
|
this.dataRepository = dataRepository; |
||||||
|
this.oldMemoryAddress = structure.oldMemoryAddress; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Loads the structure from the given stream during instance creation. |
||||||
|
* @param inputStream |
||||||
|
* the stream we read the structure from |
||||||
|
* @param names |
||||||
|
* the names from which the name of structure and its fields will be taken |
||||||
|
* @param types |
||||||
|
* the names of types for the structure |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception occurs if the amount of fields, defined in the file, is negative |
||||||
|
*/ |
||||||
|
public Structure(BlenderInputStream inputStream, String[] names, String[] types, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
int nameIndex = inputStream.readShort(); |
||||||
|
type = types[nameIndex]; |
||||||
|
this.dataRepository = dataRepository; |
||||||
|
int fieldsAmount = inputStream.readShort(); |
||||||
|
if(fieldsAmount < 0) { |
||||||
|
throw new BlenderFileException("The amount of fields of " + this.type + " structure cannot be negative!"); |
||||||
|
} |
||||||
|
if(fieldsAmount > 0) { |
||||||
|
fields = new Field[fieldsAmount]; |
||||||
|
for(int i = 0; i < fieldsAmount; ++i) { |
||||||
|
int typeIndex = inputStream.readShort(); |
||||||
|
nameIndex = inputStream.readShort(); |
||||||
|
fields[i] = new Field(names[nameIndex], types[typeIndex], dataRepository); |
||||||
|
} |
||||||
|
} |
||||||
|
this.oldMemoryAddress = Long.valueOf(-1L); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method fills the structure with data. |
||||||
|
* @param inputStream |
||||||
|
* the stream we read data from, its read cursor should be placed at the start position of the data for the |
||||||
|
* structure |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when the blend file is somehow invalid or corrupted |
||||||
|
*/ |
||||||
|
public void fill(BlenderInputStream inputStream) throws BlenderFileException { |
||||||
|
int position = inputStream.getPosition(); |
||||||
|
inputStream.setPosition(position - 8 - inputStream.getPointerSize()); |
||||||
|
this.oldMemoryAddress = Long.valueOf(inputStream.readPointer()); |
||||||
|
inputStream.setPosition(position); |
||||||
|
for(Field field : fields) { |
||||||
|
field.fill(inputStream); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the value of the filed with a given name. |
||||||
|
* @param fieldName |
||||||
|
* the name of the field |
||||||
|
* @return the value of the field or null if no field with a given name is found |
||||||
|
*/ |
||||||
|
public Object getFieldValue(String fieldName) { |
||||||
|
for(Field field : fields) { |
||||||
|
if(field.name.equalsIgnoreCase(fieldName)) { |
||||||
|
return field.value; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the value of the filed with a given name. The structure is considered to have flat fields |
||||||
|
* only (no substructures). |
||||||
|
* @param fieldName |
||||||
|
* the name of the field |
||||||
|
* @return the value of the field or null if no field with a given name is found |
||||||
|
*/ |
||||||
|
public Object getFlatFieldValue(String fieldName) { |
||||||
|
for(Field field : fields) { |
||||||
|
Object value = field.value; |
||||||
|
if(field.name.equalsIgnoreCase(fieldName)) { |
||||||
|
return value; |
||||||
|
} else if(value instanceof Structure) { |
||||||
|
value = ((Structure)value).getFlatFieldValue(fieldName); |
||||||
|
if(value != null) {//we can compare references here, since we use one static object as a NULL field value
|
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This methos should be used on structures that are of a 'ListBase' type. It creates a List of structures that are |
||||||
|
* held by this structure within the blend file. |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return a list of filled structures |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blend file structure is somehow invalid or corrupted |
||||||
|
* @throws IllegalArgumentException |
||||||
|
* this exception is thrown if the type of the structure is not 'ListBase' |
||||||
|
*/ |
||||||
|
public List<Structure> evaluateListBase(DataRepository dataRepository) throws BlenderFileException { |
||||||
|
if(!"ListBase".equals(this.type)) { |
||||||
|
throw new IllegalStateException("This structure is not of type: 'ListBase'"); |
||||||
|
} |
||||||
|
Pointer first = (Pointer)this.getFieldValue("first"); |
||||||
|
Pointer last = (Pointer)this.getFieldValue("last"); |
||||||
|
long currentAddress = 0; |
||||||
|
long lastAddress = last.getOldMemoryAddress(); |
||||||
|
List<Structure> result = new LinkedList<Structure>(); |
||||||
|
while(currentAddress != lastAddress) { |
||||||
|
currentAddress = first.getOldMemoryAddress(); |
||||||
|
Structure structure = first.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
result.add(structure); |
||||||
|
first = (Pointer)structure.getFlatFieldValue("next"); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the type of the structure. |
||||||
|
* @return the type of the structure |
||||||
|
*/ |
||||||
|
public String getType() { |
||||||
|
return type; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the amount of fields for the current structure. |
||||||
|
* @return the amount of fields for the current structure |
||||||
|
*/ |
||||||
|
public int getFieldsAmount() { |
||||||
|
return fields.length; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the field name of the given index. |
||||||
|
* @param fieldIndex |
||||||
|
* the index of the field |
||||||
|
* @return the field name of the given index |
||||||
|
*/ |
||||||
|
public String getFieldName(int fieldIndex) { |
||||||
|
return fields[fieldIndex].name; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the field type of the given index. |
||||||
|
* @param fieldIndex |
||||||
|
* the index of the field |
||||||
|
* @return the field type of the given index |
||||||
|
*/ |
||||||
|
public String getFieldType(int fieldIndex) { |
||||||
|
return fields[fieldIndex].type; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the address of the structure. The strucutre should be filled with data otherwise an exception |
||||||
|
* is thrown. |
||||||
|
* @return the address of the feature stored in this structure |
||||||
|
*/ |
||||||
|
public Long getOldMemoryAddress() { |
||||||
|
if(oldMemoryAddress.longValue() == -1L) { |
||||||
|
throw new IllegalStateException("Call the 'fill' method and fill the structure with data first!"); |
||||||
|
} |
||||||
|
return oldMemoryAddress; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the name of the structure. If the structure has an ID field then the name is returned. |
||||||
|
* Otherwise the name does not exists and the method returns null. |
||||||
|
* @return the name of the structure read from the ID field or null |
||||||
|
*/ |
||||||
|
public String getName() { |
||||||
|
Structure id = (Structure)this.getFieldValue("ID"); |
||||||
|
return id == null ? null : id.getFieldValue("name").toString().substring(2);//blender adds 2-charactes as a name prefix
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n"); |
||||||
|
for(int i = 0; i < fields.length; ++i) { |
||||||
|
result.append(fields[i].toString()).append('\n'); |
||||||
|
} |
||||||
|
return result.append('}').toString(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object clone() throws CloneNotSupportedException { |
||||||
|
return new Structure(this, dataRepository); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This enum enumerates all known data types that can be found in the blend file. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
/*package*/static enum DataType { |
||||||
|
CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER; |
||||||
|
|
||||||
|
/** The map containing the known primary types. */ |
||||||
|
private static final Map<String, DataType> PRIMARY_TYPES = new HashMap<String, DataType>(10); |
||||||
|
static { |
||||||
|
PRIMARY_TYPES.put("char", CHARACTER); |
||||||
|
PRIMARY_TYPES.put("uchar", CHARACTER); |
||||||
|
PRIMARY_TYPES.put("short", SHORT); |
||||||
|
PRIMARY_TYPES.put("ushort", SHORT); |
||||||
|
PRIMARY_TYPES.put("int", INTEGER); |
||||||
|
PRIMARY_TYPES.put("long", LONG); |
||||||
|
PRIMARY_TYPES.put("ulong", LONG); |
||||||
|
PRIMARY_TYPES.put("float", FLOAT); |
||||||
|
PRIMARY_TYPES.put("double", DOUBLE); |
||||||
|
PRIMARY_TYPES.put("void", VOID); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the data type that is appropriate to the given type name. WARNING! The type recognition |
||||||
|
* is case sensitive! |
||||||
|
* @param type |
||||||
|
* the type name of the data |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return appropriate enum value to the given type name |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown if the given type name does not exist in the blend file |
||||||
|
*/ |
||||||
|
public static DataType getDataType(String type, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
DataType result = PRIMARY_TYPES.get(type); |
||||||
|
if(result != null) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
if(dataRepository.getDnaBlockData().hasStructure(type)) { |
||||||
|
return STRUCTURE; |
||||||
|
} |
||||||
|
throw new BlenderFileException("Unknown data type: " + type); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.helpers; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.scene.plugins.blender.data.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.structures.BezierCurve; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Ipo; |
||||||
|
import com.jme3.scene.plugins.blender.utils.BlenderInputStream; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* This class defines the methods to calculate certain aspects of animation and armature functionalities. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class ArmatureHelper extends com.jme3.scene.plugins.blender.helpers.v249.ArmatureHelper { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName()); |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in |
||||||
|
* different blender versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public ArmatureHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BoneTrack[] getTracks(Structure actionStructure, DataRepository dataRepository, String objectName, String animationName) throws BlenderFileException { |
||||||
|
if(blenderVersion<250) { |
||||||
|
return super.getTracks(actionStructure, dataRepository, objectName, animationName); |
||||||
|
} |
||||||
|
LOGGER.log(Level.INFO, "Getting tracks!"); |
||||||
|
int fps = dataRepository.getBlenderKey().getFps(); |
||||||
|
int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, animationName); |
||||||
|
Structure groups = (Structure)actionStructure.getFieldValue("groups"); |
||||||
|
List<Structure> actionGroups = groups.evaluateListBase(dataRepository);//bActionGroup
|
||||||
|
if(actionGroups != null && actionGroups.size() > 0 && (bonesMap == null || bonesMap.size() == 0)) { |
||||||
|
throw new IllegalStateException("No bones found! Cannot proceed to calculating tracks!"); |
||||||
|
} |
||||||
|
|
||||||
|
List<BoneTrack> tracks = new ArrayList<BoneTrack>(); |
||||||
|
for(Structure actionGroup : actionGroups) { |
||||||
|
String name = actionGroup.getFieldValue("name").toString(); |
||||||
|
Integer boneIndex = bonesMap.get(name); |
||||||
|
if(boneIndex != null) { |
||||||
|
List<Structure> channels = ((Structure)actionGroup.getFieldValue("channels")).evaluateListBase(dataRepository); |
||||||
|
BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; |
||||||
|
int channelCounter = 0; |
||||||
|
for(Structure c : channels) { |
||||||
|
//reading rna path first
|
||||||
|
BlenderInputStream bis = dataRepository.getInputStream(); |
||||||
|
int currentPosition = bis.getPosition(); |
||||||
|
Pointer pRnaPath = (Pointer) c.getFieldValue("rna_path"); |
||||||
|
FileBlockHeader dataFileBlock = dataRepository.getFileBlock(pRnaPath.getOldMemoryAddress()); |
||||||
|
bis.setPosition(dataFileBlock.getBlockPosition()); |
||||||
|
String rnaPath = bis.readString(); |
||||||
|
bis.setPosition(currentPosition); |
||||||
|
int arrayIndex = ((Number)c.getFieldValue("array_index")).intValue(); |
||||||
|
int type = this.getCurveType(rnaPath, arrayIndex); |
||||||
|
|
||||||
|
Pointer pBezTriple = (Pointer)c.getFieldValue("bezt"); |
||||||
|
List<Structure> bezTriples = pBezTriple.fetchData(dataRepository.getInputStream()); |
||||||
|
bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); |
||||||
|
} |
||||||
|
|
||||||
|
Ipo ipo = new Ipo(bezierCurves); |
||||||
|
tracks.add(ipo.calculateTrack(boneIndex.intValue(), animationFrames[0], animationFrames[1], fps)); |
||||||
|
} |
||||||
|
} |
||||||
|
return tracks.toArray(new BoneTrack[tracks.size()]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method parses the information stored inside the curve rna path and returns the proper type |
||||||
|
* of the curve. |
||||||
|
* @param rnaPath the curve's rna path |
||||||
|
* @param arrayIndex the array index of the stored data |
||||||
|
* @return the type of the curve |
||||||
|
*/ |
||||||
|
protected int getCurveType(String rnaPath, int arrayIndex) { |
||||||
|
if(rnaPath.endsWith(".location")) { |
||||||
|
return Ipo.AC_LOC_X + arrayIndex; |
||||||
|
} |
||||||
|
if(rnaPath.endsWith(".rotation_quaternion")) { |
||||||
|
return Ipo.AC_QUAT_W + arrayIndex; |
||||||
|
} |
||||||
|
if(rnaPath.endsWith(".scale")) { |
||||||
|
return Ipo.AC_SIZE_X + arrayIndex; |
||||||
|
} |
||||||
|
throw new IllegalStateException("Unknown curve rna path: " + rnaPath); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,370 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.VertexBuffer; |
||||||
|
import com.jme3.scene.VertexBuffer.Type; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Ipo; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class defines the methods to calculate certain aspects of animation and armature functionalities. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class ArmatureHelper extends AbstractBlenderHelper { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName()); |
||||||
|
|
||||||
|
/** |
||||||
|
* The map of the bones. Maps a bone name to its index in the armature. Should be cleared after the object had been |
||||||
|
* read. TODO: probably bones can have identical names in different armatures |
||||||
|
*/ |
||||||
|
protected Map<String, Integer> bonesMap = new HashMap<String, Integer>(); |
||||||
|
/** A map of bones and their old memory addresses. */ |
||||||
|
protected Map<Bone, Long> bonesOMAs = new HashMap<Bone, Long>(); |
||||||
|
/** This list contains bones hierarchy and their matrices. It is later converted into jme bones. */ |
||||||
|
protected List<BoneTransformationData> boneDataRoots = new ArrayList<BoneTransformationData>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in |
||||||
|
* different blender versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public ArmatureHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the old memory address of a bone. If the bone does not exist in the blend file - zero is |
||||||
|
* returned. |
||||||
|
* @param bone |
||||||
|
* the bone whose old memory address we seek |
||||||
|
* @return the old memory address of the given bone |
||||||
|
*/ |
||||||
|
public Long getBoneOMA(Bone bone) { |
||||||
|
Long result = bonesOMAs.get(bone); |
||||||
|
if(result == null) { |
||||||
|
result = Long.valueOf(0); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads the bones and returns an empty skeleton. Bones should be assigned later. |
||||||
|
* @param structure |
||||||
|
* armature structure |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return an empty skeleton, bones are stored within the helper object |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow corrupted |
||||||
|
*/ |
||||||
|
public Skeleton toArmature(Structure structure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
LOGGER.log(Level.INFO, "Converting structure to Armature!"); |
||||||
|
Structure bonebase = (Structure)structure.getFieldValue("bonebase"); |
||||||
|
List<Structure> bonesStructures = bonebase.evaluateListBase(dataRepository); |
||||||
|
for(Structure boneStructure : bonesStructures) { |
||||||
|
BoneTransformationData rootBoneTransformationData = this.readBoneAndItsChildren(boneStructure, null, dataRepository); |
||||||
|
boneDataRoots.add(rootBoneTransformationData); |
||||||
|
} |
||||||
|
return new Skeleton();//bones are assigned later
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a map where the key is the object's group index that is used by a bone and the key is the |
||||||
|
* bone index in the armature. |
||||||
|
* @param poseStructure |
||||||
|
* a bPose structure of the object |
||||||
|
* @return bone group-to-index map |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow corrupted |
||||||
|
*/ |
||||||
|
public Map<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
Map<Integer, Integer> result = null; |
||||||
|
if(bonesMap != null && bonesMap.size() != 0) { |
||||||
|
result = new HashMap<Integer, Integer>(); |
||||||
|
List<Structure> deformGroups = defBaseStructure.evaluateListBase(dataRepository);//bDeformGroup
|
||||||
|
int groupIndex = 0;//TODO: consider many armatures attached to one object in the future !!!
|
||||||
|
for(Structure deformGroup : deformGroups) { |
||||||
|
String deformGroupName = deformGroup.getFieldValue("name").toString(); |
||||||
|
Integer boneIndex = bonesMap.get(deformGroupName); |
||||||
|
if(boneIndex != null) { |
||||||
|
result.put(Integer.valueOf(groupIndex), boneIndex); |
||||||
|
} |
||||||
|
++groupIndex; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads the tracks of the armature object. |
||||||
|
* @param actionStructure |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @param objectName |
||||||
|
* the name of animation owner |
||||||
|
* @param animationName |
||||||
|
* the name of the animation |
||||||
|
* @return a list of tracks for the armature |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow corrupted |
||||||
|
*/ |
||||||
|
public BoneTrack[] getTracks(Structure actionStructure, DataRepository dataRepository, String objectName, String animationName) throws BlenderFileException { |
||||||
|
LOGGER.log(Level.INFO, "Getting tracks!"); |
||||||
|
IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class); |
||||||
|
int fps = dataRepository.getBlenderKey().getFps(); |
||||||
|
int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, animationName); |
||||||
|
Structure chanbase = (Structure)actionStructure.getFieldValue("chanbase"); |
||||||
|
List<Structure> actionChannels = chanbase.evaluateListBase(dataRepository);//bActionChannel
|
||||||
|
if(actionChannels != null && actionChannels.size() > 0 && (bonesMap == null || bonesMap.size() == 0)) { |
||||||
|
throw new IllegalStateException("No bones found! Cannot proceed to calculating tracks!"); |
||||||
|
} |
||||||
|
List<BoneTrack> tracks = new ArrayList<BoneTrack>(); |
||||||
|
for(Structure bActionChannel : actionChannels) { |
||||||
|
String name = bActionChannel.getFieldValue("name").toString(); |
||||||
|
Integer boneIndex = bonesMap.get(name); |
||||||
|
if(boneIndex != null) { |
||||||
|
Pointer p = (Pointer)bActionChannel.getFieldValue("ipo"); |
||||||
|
if(!p.isNull()) { |
||||||
|
Structure ipoStructure = p.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
Ipo ipo = ipoHelper.createIpo(ipoStructure, dataRepository); |
||||||
|
tracks.add(ipo.calculateTrack(boneIndex.intValue(), animationFrames[0], animationFrames[1], fps)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return tracks.toArray(new BoneTrack[tracks.size()]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This bone returns transformation matrix of the bone that is relative to |
||||||
|
* its armature object. |
||||||
|
* @param boneStructure the bone's structure |
||||||
|
* @return bone's transformation matrix in armature space |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
protected Matrix4f getArmatureMatrix(Structure boneStructure) { |
||||||
|
DynamicArray<Number> boneMat = (DynamicArray<Number>)boneStructure.getFieldValue("arm_mat"); |
||||||
|
Matrix4f m = new Matrix4f(); |
||||||
|
for(int i = 0; i < 4; ++i) { |
||||||
|
for(int j = 0; j < 4; ++j) { |
||||||
|
m.set(i, j, boneMat.get(j, i).floatValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
return m; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads the bone with its children. |
||||||
|
* @param boneStructure |
||||||
|
* a structure containing the bone data |
||||||
|
* @param parent |
||||||
|
* the bone parent; if null then we read the root bone |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return the bone transformation data; contains bone chierarchy and the bone's matrices |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow corrupted |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
protected BoneTransformationData readBoneAndItsChildren(Structure boneStructure, BoneTransformationData parent, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
String name = boneStructure.getFieldValue("name").toString(); |
||||||
|
Bone bone = new Bone(name); |
||||||
|
int bonesAmount = bonesOMAs.size(); |
||||||
|
bonesOMAs.put(bone, boneStructure.getOldMemoryAddress()); |
||||||
|
if(bonesAmount == bonesOMAs.size()) { |
||||||
|
throw new IllegalStateException("Two bones has the same hash value and thereforw a bone was overriden in the bones<->OMA map! Improve the hash algorithm!"); |
||||||
|
} |
||||||
|
Matrix4f boneArmatureMatrix = this.getArmatureMatrix(boneStructure); |
||||||
|
DynamicArray<Float> sizeArray = (DynamicArray<Float>) boneStructure.getFieldValue("size"); |
||||||
|
Vector3f size = new Vector3f(sizeArray.get(0), sizeArray.get(1), sizeArray.get(2)); |
||||||
|
BoneTransformationData boneTransformationData = new BoneTransformationData(boneArmatureMatrix, size, bone, parent); |
||||||
|
dataRepository.addLoadedFeatures(boneStructure.getOldMemoryAddress(), name, boneStructure, bone); |
||||||
|
|
||||||
|
Structure childbase = (Structure)boneStructure.getFieldValue("childbase"); |
||||||
|
List<Structure> children = childbase.evaluateListBase(dataRepository);//Bone
|
||||||
|
for(Structure boneChild : children) { |
||||||
|
this.readBoneAndItsChildren(boneChild, boneTransformationData, dataRepository); |
||||||
|
} |
||||||
|
return boneTransformationData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method assigns transformations to the bone. |
||||||
|
* @param btd |
||||||
|
* the bone data containing the bone we assign transformation to |
||||||
|
* @param additionalRootBoneTransformation |
||||||
|
* additional bone transformation which indicates it's mesh parent and armature object transformations |
||||||
|
* @param boneList |
||||||
|
* a list of all read bones |
||||||
|
*/ |
||||||
|
protected void assignBonesMatrices(BoneTransformationData btd, Matrix4f additionalRootBoneTransformation, List<Bone> boneList) { |
||||||
|
LOGGER.info("[" + btd.bone.getName() + "] additionalRootBoneTransformation =\n" + additionalRootBoneTransformation); |
||||||
|
Matrix4f totalInverseParentMatrix = btd.parent != null ? btd.parent.totalInverseBoneParentMatrix : Matrix4f.IDENTITY; |
||||||
|
LOGGER.info("[" + btd.bone.getName() + "] totalInverseParentMatrix =\n" + totalInverseParentMatrix); |
||||||
|
Matrix4f restMatrix = additionalRootBoneTransformation.mult(btd.boneArmatureMatrix); |
||||||
|
LOGGER.info("[" + btd.bone.getName() + "] restMatrix =\n" + restMatrix); |
||||||
|
btd.totalInverseBoneParentMatrix = restMatrix.clone().invert(); |
||||||
|
restMatrix = totalInverseParentMatrix.mult(restMatrix); |
||||||
|
LOGGER.info("[" + btd.bone.getName() + "] resultMatrix =\n" + restMatrix); |
||||||
|
btd.bone.setBindTransforms(restMatrix.toTranslationVector(), restMatrix.toRotationQuat(), btd.size); |
||||||
|
boneList.add(btd.bone); |
||||||
|
bonesMap.put(btd.bone.getName(), Integer.valueOf(boneList.size() - 1)); |
||||||
|
if(btd.children != null && btd.children.size() > 0) { |
||||||
|
for(BoneTransformationData child : btd.children) { |
||||||
|
this.assignBonesMatrices(child, additionalRootBoneTransformation, boneList); |
||||||
|
btd.bone.addChild(child.bone); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns bone transformation data for the bone of a given name. |
||||||
|
* @param boneName |
||||||
|
* the name of the bone |
||||||
|
* @return bone's transformation data |
||||||
|
*/ |
||||||
|
public BoneTransformationData getBoneTransformationDataRoot(int index) { |
||||||
|
return boneDataRoots.get(index); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the amount of bones transformations roots. |
||||||
|
* @return the amount of bones transformations roots |
||||||
|
*/ |
||||||
|
public int getBoneTransformationDataRootsSize() { |
||||||
|
return boneDataRoots.size(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This class holds the data needed later for bone transformation calculation and to bind parent with children. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
private static class BoneTransformationData { |
||||||
|
/** Inverse matrix of bone's parent bone. */ |
||||||
|
private Matrix4f totalInverseBoneParentMatrix; |
||||||
|
/** Bone's matrix in armature's space. */ |
||||||
|
private Matrix4f boneArmatureMatrix; |
||||||
|
/** Bone's size (apparently it is held outside the transformation matrix. */ |
||||||
|
private Vector3f size; |
||||||
|
/** The bone the data applies to. */ |
||||||
|
private Bone bone; |
||||||
|
/** The parent of the above mentioned bone (not assigned yet). */ |
||||||
|
private BoneTransformationData parent; |
||||||
|
/** The children of the current bone. */ |
||||||
|
private List<BoneTransformationData> children; |
||||||
|
|
||||||
|
/** |
||||||
|
* Private constructor creates the object and assigns the given data. |
||||||
|
* @param boneArmatureMatrix |
||||||
|
* the matrix of the current bone |
||||||
|
* @param size |
||||||
|
* the bone's size |
||||||
|
* @param bone |
||||||
|
* the current bone |
||||||
|
* @param parent |
||||||
|
* the parent structure of the bone |
||||||
|
*/ |
||||||
|
private BoneTransformationData(Matrix4f boneArmatureMatrix, Vector3f size, Bone bone, BoneTransformationData parent) { |
||||||
|
this.boneArmatureMatrix = boneArmatureMatrix; |
||||||
|
this.size = size; |
||||||
|
this.bone = bone; |
||||||
|
this.parent = parent; |
||||||
|
this.children = new ArrayList<ArmatureHelper.BoneTransformationData>(); |
||||||
|
if(this.parent != null) { |
||||||
|
this.parent.children.add(this); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method creates the whole bones structure. Assignes transformations to bones and combines children with |
||||||
|
* parents. |
||||||
|
* @param armatureOMA |
||||||
|
* old memory address of bones' armature object |
||||||
|
* @param additionalRootBoneTransformation |
||||||
|
* additional bone transformation which indicates it's mesh parent and armature object transformations |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public Bone[] buildBonesStructure(Long armatureOMA, Matrix4f additionalRootBoneTransformation) {//TODO: uwzględnić wiele szkieletów
|
||||||
|
List<Bone> bones = new ArrayList<Bone>(boneDataRoots.size() + 1); |
||||||
|
bones.add(new Bone(null)); |
||||||
|
for(BoneTransformationData btd : boneDataRoots) { |
||||||
|
this.assignBonesMatrices(btd, additionalRootBoneTransformation, bones); |
||||||
|
} |
||||||
|
return bones.toArray(new Bone[bones.size()]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method assigns an immovable bone to vertices that have no bone assigned. They have the bone index with the |
||||||
|
* value -1. |
||||||
|
* @param immovableBoneIndex |
||||||
|
* the ondex of immovable bone |
||||||
|
* @param meshes |
||||||
|
* a list of meshes whose vertices will be assigned to immovable bone |
||||||
|
*/ |
||||||
|
public void assignBoneToOrphanedVertices(byte immovableBoneIndex, Mesh[] meshes) { |
||||||
|
//bone indices are common for all the object's meshes (vertex indices specify which are to be used)
|
||||||
|
VertexBuffer boneIndices = meshes[0].getBuffer(Type.BoneIndex);//common buffer to all the meshes
|
||||||
|
ByteBuffer data = (ByteBuffer)boneIndices.getData(); |
||||||
|
for(int i = 0; i < boneIndices.getNumElements(); ++i) { |
||||||
|
if(data.get(i) == -1) { |
||||||
|
data.put(i, immovableBoneIndex); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void clearState() { |
||||||
|
bonesMap.clear(); |
||||||
|
boneDataRoots.clear(); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,734 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.BoneAnimation; |
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.structures.AbstractInfluenceFunction; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Constraint; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Constraint.Space; |
||||||
|
import com.jme3.scene.plugins.blender.structures.ConstraintType; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Ipo; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class should be used for constraint calculations. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class ConstraintHelper extends AbstractBlenderHelper { |
||||||
|
/** |
||||||
|
* A table containing implementations of influence functions for constraints. It should contain functions for |
||||||
|
* blender at least 249 and higher. |
||||||
|
*/ |
||||||
|
protected static AbstractInfluenceFunction[] influenceFunctions; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constraints stored for object with the given old memory address. |
||||||
|
*/ |
||||||
|
protected Map<Long, Constraint[]> constraints = new HashMap<Long, Constraint[]>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Helper constructor. It's main task is to generate the affection functions. These functions are common to all |
||||||
|
* ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall |
||||||
|
* consider refactoring. The constructor parses the given blender version and stores the result. Some |
||||||
|
* functionalities may differ in different blender versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public ConstraintHelper(String blenderVersion, DataRepository dataRepository) { |
||||||
|
super(blenderVersion); |
||||||
|
if(influenceFunctions == null) { |
||||||
|
//TODO: synchronization
|
||||||
|
influenceFunctions = new AbstractInfluenceFunction[ConstraintType.getLastDefinedTypeValue() + 1]; |
||||||
|
//ACTION constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_ACTION.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_ACTION, dataRepository) {}; |
||||||
|
|
||||||
|
//CHILDOF constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_CHILDOF.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_CHILDOF, dataRepository) {}; |
||||||
|
|
||||||
|
//CLAMPTO constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_CLAMPTO.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_CLAMPTO, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
this.validateConstraintType(constraint.getData()); |
||||||
|
LOGGER.info(constraint.getName() + " not active! Curves not yet implemented!");//TODO: implement when curves are implemented
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//DISTLIMIT constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_DISTLIMIT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_DISTLIMIT, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
Structure constraintStructure = constraint.getData(); |
||||||
|
this.validateConstraintType(constraintStructure); |
||||||
|
Vector3f targetLocation = this.getTargetLocation(constraint); |
||||||
|
BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint); |
||||||
|
if(boneTrack != null) { |
||||||
|
//TODO: target vertex group !!!
|
||||||
|
float dist = ((Number)constraintStructure.getFieldValue("dist")).floatValue(); |
||||||
|
int mode = ((Number)constraintStructure.getFieldValue("mode")).intValue(); |
||||||
|
|
||||||
|
int maxFrames = boneTrack.getTimes().length; |
||||||
|
Vector3f[] translations = boneTrack.getTranslations(); |
||||||
|
for(int frame = 0; frame < maxFrames; ++frame) { |
||||||
|
Vector3f v = translations[frame].subtract(targetLocation); |
||||||
|
float currentDistance = v.length(); |
||||||
|
float influence = constraint.getIpo().calculateValue(frame); |
||||||
|
float modifier = 0.0f; |
||||||
|
switch(mode) { |
||||||
|
case LIMITDIST_INSIDE: |
||||||
|
if(currentDistance >= dist) { |
||||||
|
modifier = (dist - currentDistance) / currentDistance; |
||||||
|
} |
||||||
|
break; |
||||||
|
case LIMITDIST_ONSURFACE: |
||||||
|
modifier = (dist - currentDistance) / currentDistance; |
||||||
|
break; |
||||||
|
case LIMITDIST_OUTSIDE: |
||||||
|
if(currentDistance <= dist) { |
||||||
|
modifier = (dist - currentDistance) / currentDistance; |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new IllegalStateException("Unknown distance limit constraint mode: " + mode); |
||||||
|
} |
||||||
|
translations[frame].addLocal(v.multLocal(modifier * influence)); |
||||||
|
} |
||||||
|
boneTrack.setKeyframes(boneTrack.getTimes(), translations, boneTrack.getRotations(), boneTrack.getScales()); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//FOLLOWPATH constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_FOLLOWPATH.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_FOLLOWPATH, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
this.validateConstraintType(constraint.getData()); |
||||||
|
LOGGER.info(constraint.getName() + " not active! Curves not yet implemented!");//TODO: implement when curves are implemented
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//KINEMATIC constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_KINEMATIC.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_KINEMATIC, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
Structure constraintStructure = constraint.getData(); |
||||||
|
this.validateConstraintType(constraintStructure); |
||||||
|
/*Long boneOMA = constraint.getBoneOMA(); |
||||||
|
//IK solver is only attached to bones
|
||||||
|
Bone ownerBone = (Bone)dataRepository.getLoadedFeature(boneOMA, LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
|
||||||
|
//get the target point
|
||||||
|
Object targetObject = this.getTarget(constraint, LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
Vector3f pt = null;//Point Target
|
||||||
|
if(targetObject instanceof Bone) { |
||||||
|
pt = ((Bone)targetObject).getModelSpacePosition(); |
||||||
|
} else if(targetObject instanceof Node) { |
||||||
|
pt = ((Node)targetObject).getWorldTranslation(); |
||||||
|
} else if(targetObject instanceof Skeleton) { |
||||||
|
Structure armatureNodeStructure = (Structure)this.getTarget(constraint, LoadedFeatureDataType.LOADED_STRUCTURE); |
||||||
|
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); |
||||||
|
Transform transform = objectHelper.getTransformation(armatureNodeStructure); |
||||||
|
pt = transform.getTranslation(); |
||||||
|
} else { |
||||||
|
throw new IllegalStateException("Unknown target object type! Should be Node, Bone or Skeleton and there is: " + targetObject.getClass().getName()); |
||||||
|
} |
||||||
|
//preparing data
|
||||||
|
int maxIterations = ((Number)constraintStructure.getFieldValue("iterations")).intValue(); |
||||||
|
CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation); |
||||||
|
for(int i=0;i<bones.length;++i) { |
||||||
|
System.out.println(Arrays.toString(bones[i].track.getTranslations())); |
||||||
|
System.out.println(Arrays.toString(bones[i].track.getRotations())); |
||||||
|
System.out.println("==============================="); |
||||||
|
} |
||||||
|
Quaternion rotation = new Quaternion(); |
||||||
|
int maxFrames = bones[0].track.getTimes().length;//all tracks should have the same amount of frames
|
||||||
|
|
||||||
|
for(int frame = 0; frame < maxFrames; ++frame) { |
||||||
|
float error = IK_SOLVER_ERROR; |
||||||
|
int iteration = 0; |
||||||
|
while(error >= IK_SOLVER_ERROR && iteration <= maxIterations) { |
||||||
|
//rotating the bones
|
||||||
|
for(int i = 0; i < bones.length - 1; ++i) { |
||||||
|
Vector3f pe = bones[i].getEndPoint(); |
||||||
|
Vector3f pc = bones[i + 1].getWorldTranslation().clone(); |
||||||
|
|
||||||
|
Vector3f peSUBpc = pe.subtract(pc).normalizeLocal(); |
||||||
|
Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal(); |
||||||
|
|
||||||
|
float theta = FastMath.acos(peSUBpc.dot(ptSUBpc)); |
||||||
|
Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal(); |
||||||
|
bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame); |
||||||
|
} |
||||||
|
error = pt.subtract(bones[0].getEndPoint()).length(); |
||||||
|
++iteration; |
||||||
|
} |
||||||
|
System.out.println("error = " + error + " iterations = " + iteration); |
||||||
|
} |
||||||
|
|
||||||
|
for(CalculationBone bone : bones) { |
||||||
|
bone.applyCalculatedTracks(); |
||||||
|
} |
||||||
|
|
||||||
|
System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); |
||||||
|
for(int i=0;i<bones.length;++i) { |
||||||
|
System.out.println(Arrays.toString(bones[i].track.getTranslations())); |
||||||
|
System.out.println(Arrays.toString(bones[i].track.getRotations())); |
||||||
|
System.out.println("==============================="); |
||||||
|
}*/ |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns bones used for rotation calculations. |
||||||
|
* @param bone |
||||||
|
* the bone to which the constraint is applied |
||||||
|
* @param skeleton |
||||||
|
* the skeleton owning the bone and its ancestors |
||||||
|
* @param boneAnimation |
||||||
|
* the bone animation data that stores the traces for the skeleton's bones |
||||||
|
* @return a list of bones to imitate the bone's movement during IK solving |
||||||
|
*/ |
||||||
|
private CalculationBone[] getBonesToCalculate(Bone bone, Skeleton skeleton, BoneAnimation boneAnimation) { |
||||||
|
List<CalculationBone> bonesList = new ArrayList<CalculationBone>(); |
||||||
|
Bone currentBone = bone; |
||||||
|
do { |
||||||
|
int boneIndex = skeleton.getBoneIndex(currentBone); |
||||||
|
for(int i = 0; i < boneAnimation.getTracks().length; ++i) { |
||||||
|
if(boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) { |
||||||
|
bonesList.add(new CalculationBone(currentBone, boneAnimation.getTracks()[i])); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
currentBone = currentBone.getParent(); |
||||||
|
} while(currentBone != null); |
||||||
|
//attaching children
|
||||||
|
CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]); |
||||||
|
for(int i = result.length - 1; i > 0; --i) { |
||||||
|
result[i].attachChild(result[i - 1]); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//LOCKTRACK constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_LOCKTRACK.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_LOCKTRACK, dataRepository) {}; |
||||||
|
|
||||||
|
//LOCLIKE constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_LOCLIKE.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_LOCLIKE, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
Structure constraintData = constraint.getData(); |
||||||
|
this.validateConstraintType(constraintData); |
||||||
|
BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint); |
||||||
|
if(boneTrack != null) { |
||||||
|
Vector3f targetLocation = this.getTargetLocation(constraint); |
||||||
|
int flag = ((Number)constraintData.getFieldValue("flag")).intValue(); |
||||||
|
Vector3f[] translations = boneTrack.getTranslations(); |
||||||
|
int maxFrames = translations.length; |
||||||
|
for(int frame = 0; frame < maxFrames; ++frame) { |
||||||
|
Vector3f offset = Vector3f.ZERO; |
||||||
|
if((flag & LOCLIKE_OFFSET) != 0) {//we add the original location to the copied location
|
||||||
|
offset = translations[frame].clone(); |
||||||
|
} |
||||||
|
|
||||||
|
if((flag & LOCLIKE_X) != 0) { |
||||||
|
translations[frame].x = targetLocation.x; |
||||||
|
if((flag & LOCLIKE_X_INVERT) != 0) { |
||||||
|
translations[frame].x = -translations[frame].x; |
||||||
|
} |
||||||
|
} else if((flag & LOCLIKE_Y) != 0) { |
||||||
|
translations[frame].y = targetLocation.y; |
||||||
|
if((flag & LOCLIKE_Y_INVERT) != 0) { |
||||||
|
translations[frame].y = -translations[frame].y; |
||||||
|
} |
||||||
|
} else if((flag & LOCLIKE_Z) != 0) { |
||||||
|
translations[frame].z = targetLocation.z; |
||||||
|
if((flag & LOCLIKE_Z_INVERT) != 0) { |
||||||
|
translations[frame].z = -translations[frame].z; |
||||||
|
} |
||||||
|
} |
||||||
|
translations[frame].addLocal(offset);//TODO: ipo influence
|
||||||
|
} |
||||||
|
boneTrack.setKeyframes(boneTrack.getTimes(), translations, boneTrack.getRotations(), boneTrack.getScales()); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//LOCLIMIT constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_LOCLIMIT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_LOCLIMIT, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
Structure constraintStructure = constraint.getData(); |
||||||
|
this.validateConstraintType(constraintStructure); |
||||||
|
BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint); |
||||||
|
if(boneTrack != null) { |
||||||
|
int flag = ((Number)constraintStructure.getFieldValue("flag")).intValue(); |
||||||
|
Vector3f[] translations = boneTrack.getTranslations(); |
||||||
|
int maxFrames = translations.length; |
||||||
|
for(int frame = 0; frame < maxFrames; ++frame) { |
||||||
|
float influence = constraint.getIpo().calculateValue(frame); |
||||||
|
if((flag & LIMIT_XMIN) != 0) { |
||||||
|
float xmin = ((Number)constraintStructure.getFieldValue("xmin")).floatValue(); |
||||||
|
if(translations[frame].x < xmin) { |
||||||
|
translations[frame].x -= (translations[frame].x - xmin) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_XMAX) != 0) { |
||||||
|
float xmax = ((Number)constraintStructure.getFieldValue("xmax")).floatValue(); |
||||||
|
if(translations[frame].x > xmax) { |
||||||
|
translations[frame].x -= (translations[frame].x - xmax) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_YMIN) != 0) { |
||||||
|
float ymin = ((Number)constraintStructure.getFieldValue("ymin")).floatValue(); |
||||||
|
if(translations[frame].y < ymin) { |
||||||
|
translations[frame].y -= (translations[frame].y - ymin) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_YMAX) != 0) { |
||||||
|
float ymax = ((Number)constraintStructure.getFieldValue("ymax")).floatValue(); |
||||||
|
if(translations[frame].y > ymax) { |
||||||
|
translations[frame].y -= (translations[frame].y - ymax) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_ZMIN) != 0) { |
||||||
|
float zmin = ((Number)constraintStructure.getFieldValue("zmin")).floatValue(); |
||||||
|
if(translations[frame].z < zmin) { |
||||||
|
translations[frame].z -= (translations[frame].z - zmin) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_ZMAX) != 0) { |
||||||
|
float zmax = ((Number)constraintStructure.getFieldValue("zmax")).floatValue(); |
||||||
|
if(translations[frame].z > zmax) { |
||||||
|
translations[frame].z -= (translations[frame].z - zmax) * influence; |
||||||
|
} |
||||||
|
}//TODO: consider constraint space !!!
|
||||||
|
} |
||||||
|
boneTrack.setKeyframes(boneTrack.getTimes(), translations, boneTrack.getRotations(), boneTrack.getScales()); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//MINMAX constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_MINMAX.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_MINMAX, dataRepository) {}; |
||||||
|
|
||||||
|
//NULL constraint - does nothing
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_NULL.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_NULL, dataRepository) {}; |
||||||
|
|
||||||
|
//PYTHON constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_PYTHON.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_PYTHON, dataRepository) {}; |
||||||
|
|
||||||
|
//RIGIDBODYJOINT constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_RIGIDBODYJOINT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_RIGIDBODYJOINT, dataRepository) {}; |
||||||
|
|
||||||
|
//ROTLIKE constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_ROTLIKE.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_ROTLIKE, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
Structure constraintData = constraint.getData(); |
||||||
|
this.validateConstraintType(constraintData); |
||||||
|
BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint); |
||||||
|
if(boneTrack != null) { |
||||||
|
Quaternion targetRotation = this.getTargetRotation(constraint); |
||||||
|
int flag = ((Number)constraintData.getFieldValue("flag")).intValue(); |
||||||
|
float[] targetAngles = targetRotation.toAngles(null); |
||||||
|
Quaternion[] rotations = boneTrack.getRotations(); |
||||||
|
int maxFrames = rotations.length; |
||||||
|
for(int frame = 0; frame < maxFrames; ++frame) { |
||||||
|
float[] angles = rotations[frame].toAngles(null); |
||||||
|
|
||||||
|
Quaternion offset = Quaternion.IDENTITY; |
||||||
|
if((flag & ROTLIKE_OFFSET) != 0) {//we add the original rotation to the copied rotation
|
||||||
|
offset = rotations[frame].clone(); |
||||||
|
} |
||||||
|
|
||||||
|
if((flag & ROTLIKE_X) != 0) { |
||||||
|
angles[0] = targetAngles[0]; |
||||||
|
if((flag & ROTLIKE_X_INVERT) != 0) { |
||||||
|
angles[0] = -angles[0]; |
||||||
|
} |
||||||
|
} else if((flag & ROTLIKE_Y) != 0) { |
||||||
|
angles[1] = targetAngles[1]; |
||||||
|
if((flag & ROTLIKE_Y_INVERT) != 0) { |
||||||
|
angles[1] = -angles[1]; |
||||||
|
} |
||||||
|
} else if((flag & ROTLIKE_Z) != 0) { |
||||||
|
angles[2] = targetAngles[2]; |
||||||
|
if((flag & ROTLIKE_Z_INVERT) != 0) { |
||||||
|
angles[2] = -angles[2]; |
||||||
|
} |
||||||
|
} |
||||||
|
rotations[frame].fromAngles(angles).multLocal(offset);//TODO: ipo influence
|
||||||
|
} |
||||||
|
boneTrack.setKeyframes(boneTrack.getTimes(), boneTrack.getTranslations(), rotations, boneTrack.getScales()); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//ROTLIMIT constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_ROTLIMIT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_ROTLIMIT, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
Structure constraintStructure = constraint.getData(); |
||||||
|
this.validateConstraintType(constraintStructure); |
||||||
|
BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint); |
||||||
|
if(boneTrack != null) { |
||||||
|
int flag = ((Number)constraintStructure.getFieldValue("flag")).intValue(); |
||||||
|
Quaternion[] rotations = boneTrack.getRotations(); |
||||||
|
int maxFrames = rotations.length; |
||||||
|
for(int frame = 0; frame < maxFrames; ++frame) { |
||||||
|
float[] angles = rotations[frame].toAngles(null); |
||||||
|
float influence = constraint.getIpo().calculateValue(frame); |
||||||
|
if((flag & LIMIT_XROT) != 0) { |
||||||
|
float xmin = ((Number)constraintStructure.getFieldValue("xmin")).floatValue() * FastMath.DEG_TO_RAD; |
||||||
|
float xmax = ((Number)constraintStructure.getFieldValue("xmax")).floatValue() * FastMath.DEG_TO_RAD; |
||||||
|
float difference = 0.0f; |
||||||
|
if(angles[0] < xmin) { |
||||||
|
difference = (angles[0] - xmin) * influence; |
||||||
|
} else if(angles[0] > xmax) { |
||||||
|
difference = (angles[0] - xmax) * influence; |
||||||
|
} |
||||||
|
angles[0] -= difference; |
||||||
|
} |
||||||
|
if((flag & LIMIT_YROT) != 0) { |
||||||
|
float ymin = ((Number)constraintStructure.getFieldValue("ymin")).floatValue() * FastMath.DEG_TO_RAD; |
||||||
|
float ymax = ((Number)constraintStructure.getFieldValue("ymax")).floatValue() * FastMath.DEG_TO_RAD; |
||||||
|
float difference = 0.0f; |
||||||
|
if(angles[1] < ymin) { |
||||||
|
difference = (angles[1] - ymin) * influence; |
||||||
|
} else if(angles[1] > ymax) { |
||||||
|
difference = (angles[1] - ymax) * influence; |
||||||
|
} |
||||||
|
angles[1] -= difference; |
||||||
|
} |
||||||
|
if((flag & LIMIT_ZROT) != 0) { |
||||||
|
float zmin = ((Number)constraintStructure.getFieldValue("zmin")).floatValue() * FastMath.DEG_TO_RAD; |
||||||
|
float zmax = ((Number)constraintStructure.getFieldValue("zmax")).floatValue() * FastMath.DEG_TO_RAD; |
||||||
|
float difference = 0.0f; |
||||||
|
if(angles[2] < zmin) { |
||||||
|
difference = (angles[2] - zmin) * influence; |
||||||
|
} else if(angles[2] > zmax) { |
||||||
|
difference = (angles[2] - zmax) * influence; |
||||||
|
} |
||||||
|
angles[2] -= difference; |
||||||
|
} |
||||||
|
rotations[frame].fromAngles(angles);//TODO: consider constraint space !!!
|
||||||
|
} |
||||||
|
boneTrack.setKeyframes(boneTrack.getTimes(), boneTrack.getTranslations(), rotations, boneTrack.getScales()); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//SHRINKWRAP constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_SHRINKWRAP.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_SHRINKWRAP, dataRepository) {}; |
||||||
|
|
||||||
|
//SIZELIKE constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_SIZELIKE.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_SIZELIKE, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
Structure constraintData = constraint.getData(); |
||||||
|
this.validateConstraintType(constraintData); |
||||||
|
Vector3f targetScale = this.getTargetLocation(constraint); |
||||||
|
BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint); |
||||||
|
if(boneTrack != null) { |
||||||
|
int flag = ((Number)constraintData.getFieldValue("flag")).intValue(); |
||||||
|
Vector3f[] scales = boneTrack.getScales(); |
||||||
|
int maxFrames = scales.length; |
||||||
|
for(int frame = 0; frame < maxFrames; ++frame) { |
||||||
|
Vector3f offset = Vector3f.ZERO; |
||||||
|
if((flag & LOCLIKE_OFFSET) != 0) {//we add the original scale to the copied scale
|
||||||
|
offset = scales[frame].clone(); |
||||||
|
} |
||||||
|
|
||||||
|
if((flag & SIZELIKE_X) != 0) { |
||||||
|
scales[frame].x = targetScale.x; |
||||||
|
} else if((flag & SIZELIKE_Y) != 0) { |
||||||
|
scales[frame].y = targetScale.y; |
||||||
|
} else if((flag & SIZELIKE_Z) != 0) { |
||||||
|
scales[frame].z = targetScale.z; |
||||||
|
} |
||||||
|
scales[frame].addLocal(offset);//TODO: ipo influence
|
||||||
|
//TODO: add or multiply???
|
||||||
|
} |
||||||
|
boneTrack.setKeyframes(boneTrack.getTimes(), boneTrack.getTranslations(), boneTrack.getRotations(), scales); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//SIZELIMIT constraint
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_SIZELIMIT.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_SIZELIMIT, dataRepository) { |
||||||
|
@Override |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation, Constraint constraint) { |
||||||
|
Structure constraintStructure = constraint.getData(); |
||||||
|
this.validateConstraintType(constraintStructure); |
||||||
|
BoneTrack boneTrack = this.getBoneTrack(skeleton, boneAnimation, constraint); |
||||||
|
if(boneTrack != null) { |
||||||
|
int flag = ((Number)constraintStructure.getFieldValue("flag")).intValue(); |
||||||
|
Vector3f[] scales = boneTrack.getScales(); |
||||||
|
int maxFrames = scales.length; |
||||||
|
for(int frame = 0; frame < maxFrames; ++frame) { |
||||||
|
float influence = constraint.getIpo().calculateValue(frame); |
||||||
|
if((flag & LIMIT_XMIN) != 0) { |
||||||
|
float xmin = ((Number)constraintStructure.getFieldValue("xmin")).floatValue(); |
||||||
|
if(scales[frame].x < xmin) { |
||||||
|
scales[frame].x -= (scales[frame].x - xmin) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_XMAX) != 0) { |
||||||
|
float xmax = ((Number)constraintStructure.getFieldValue("xmax")).floatValue(); |
||||||
|
if(scales[frame].x > xmax) { |
||||||
|
scales[frame].x -= (scales[frame].x - xmax) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_YMIN) != 0) { |
||||||
|
float ymin = ((Number)constraintStructure.getFieldValue("ymin")).floatValue(); |
||||||
|
if(scales[frame].y < ymin) { |
||||||
|
scales[frame].y -= (scales[frame].y - ymin) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_YMAX) != 0) { |
||||||
|
float ymax = ((Number)constraintStructure.getFieldValue("ymax")).floatValue(); |
||||||
|
if(scales[frame].y > ymax) { |
||||||
|
scales[frame].y -= (scales[frame].y - ymax) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_ZMIN) != 0) { |
||||||
|
float zmin = ((Number)constraintStructure.getFieldValue("zmin")).floatValue(); |
||||||
|
if(scales[frame].z < zmin) { |
||||||
|
scales[frame].z -= (scales[frame].z - zmin) * influence; |
||||||
|
} |
||||||
|
} |
||||||
|
if((flag & LIMIT_ZMAX) != 0) { |
||||||
|
float zmax = ((Number)constraintStructure.getFieldValue("zmax")).floatValue(); |
||||||
|
if(scales[frame].z > zmax) { |
||||||
|
scales[frame].z -= (scales[frame].z - zmax) * influence; |
||||||
|
} |
||||||
|
}//TODO: consider constraint space !!!
|
||||||
|
} |
||||||
|
boneTrack.setKeyframes(boneTrack.getTimes(), boneTrack.getTranslations(), boneTrack.getRotations(), scales); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
//STRETCHTO constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_STRETCHTO.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_STRETCHTO, dataRepository) {}; |
||||||
|
|
||||||
|
//TRANSFORM constraint (TODO: to implement)
|
||||||
|
influenceFunctions[ConstraintType.CONSTRAINT_TYPE_TRANSFORM.getConstraintId()] = new AbstractInfluenceFunction(ConstraintType.CONSTRAINT_TYPE_TRANSFORM, dataRepository) {}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads constraints for for the given structure. The constraints are loaded only once for object/bone. |
||||||
|
* @param ownerOMA |
||||||
|
* the owner's old memory address |
||||||
|
* @param objectStructure |
||||||
|
* the structure we read constraint's for |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @throws BlenderFileException |
||||||
|
*/ |
||||||
|
public void loadConstraints(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
// reading influence ipos for the constraints
|
||||||
|
IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class); |
||||||
|
Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>(); |
||||||
|
Pointer pActions = (Pointer)objectStructure.getFieldValue("action"); |
||||||
|
if(!pActions.isNull()) { |
||||||
|
List<Structure> actions = pActions.fetchData(dataRepository.getInputStream()); |
||||||
|
for(Structure action : actions) { |
||||||
|
Structure chanbase = (Structure)action.getFieldValue("chanbase"); |
||||||
|
List<Structure> actionChannels = chanbase.evaluateListBase(dataRepository); |
||||||
|
for(Structure actionChannel : actionChannels) { |
||||||
|
Map<String, Ipo> ipos = new HashMap<String, Ipo>(); |
||||||
|
Structure constChannels = (Structure)actionChannel.getFieldValue("constraintChannels"); |
||||||
|
List<Structure> constraintChannels = constChannels.evaluateListBase(dataRepository); |
||||||
|
for(Structure constraintChannel : constraintChannels) { |
||||||
|
Pointer pIpo = (Pointer)constraintChannel.getFieldValue("ipo"); |
||||||
|
if(!pIpo.isNull()) { |
||||||
|
String constraintName = constraintChannel.getFieldValue("name").toString(); |
||||||
|
Ipo ipo = ipoHelper.createIpo(pIpo.fetchData(dataRepository.getInputStream()).get(0), dataRepository); |
||||||
|
ipos.put(constraintName, ipo); |
||||||
|
} |
||||||
|
} |
||||||
|
String actionName = actionChannel.getFieldValue("name").toString(); |
||||||
|
constraintsIpos.put(actionName, ipos); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//loading constraints connected with the object's bones
|
||||||
|
List<Constraint> constraintsList = new ArrayList<Constraint>(); |
||||||
|
Pointer pPose = (Pointer)objectStructure.getFieldValue("pose");//TODO: what if the object has two armatures ????
|
||||||
|
if(!pPose.isNull()) { |
||||||
|
//getting pose channels
|
||||||
|
List<Structure> poseChannels = ((Structure)pPose.fetchData(dataRepository.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(dataRepository); |
||||||
|
for(Structure poseChannel : poseChannels) { |
||||||
|
Long boneOMA = Long.valueOf(((Pointer)poseChannel.getFieldValue("bone")).getOldMemoryAddress()); |
||||||
|
//the name is read directly from structure because bone might not yet be loaded
|
||||||
|
String name = dataRepository.getFileBlock(boneOMA).getStructure(dataRepository).getFieldValue("name").toString(); |
||||||
|
List<Structure> constraints = ((Structure)poseChannel.getFieldValue("constraints")).evaluateListBase(dataRepository); |
||||||
|
for(Structure constraint : constraints) { |
||||||
|
int type = ((Number)constraint.getFieldValue("type")).intValue(); |
||||||
|
String constraintName = constraint.getFieldValue("name").toString(); |
||||||
|
Ipo ipo = constraintsIpos.get(name).get(constraintName); |
||||||
|
if(ipo == null) { |
||||||
|
float enforce = ((Number)constraint.getFieldValue("enforce")).floatValue(); |
||||||
|
ipo = ipoHelper.createIpo(enforce); |
||||||
|
} |
||||||
|
Space ownerSpace = Space.valueOf(((Number)constraint.getFieldValue("ownspace")).byteValue()); |
||||||
|
Space targetSpace = Space.valueOf(((Number)constraint.getFieldValue("tarspace")).byteValue()); |
||||||
|
Constraint c = new Constraint(constraint, influenceFunctions[type], boneOMA, ownerSpace, targetSpace, ipo, dataRepository); |
||||||
|
constraintsList.add(c); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
/* TODO: reading constraints for objects (implement when object's animation will be available) |
||||||
|
List<Structure> constraintChannels = ((Structure)objectStructure.getFieldValue("constraintChannels")).evaluateListBase(dataRepository); |
||||||
|
for(Structure constraintChannel : constraintChannels) { |
||||||
|
System.out.println(constraintChannel); |
||||||
|
} |
||||||
|
|
||||||
|
//loading constraints connected with the object itself (TODO: test this)
|
||||||
|
if(!this.constraints.containsKey(objectStructure.getOldMemoryAddress())) { |
||||||
|
List<Structure> constraints = ((Structure)objectStructure.getFieldValue("constraints")).evaluateListBase(dataRepository); |
||||||
|
Constraint[] result = new Constraint[constraints.size()]; |
||||||
|
int i = 0; |
||||||
|
for(Structure constraint : constraints) { |
||||||
|
int type = ((Number)constraint.getFieldValue("type")).intValue(); |
||||||
|
String name = constraint.getFieldValue("name").toString(); |
||||||
|
result[i++] = new Constraint(constraint, influenceFunctions[type], null, dataRepository);//TODO: influence ipos for object animation
|
||||||
|
} |
||||||
|
this.constraints.put(objectStructure.getOldMemoryAddress(), result); |
||||||
|
} |
||||||
|
*/ |
||||||
|
if(constraintsList.size() > 0) { |
||||||
|
this.constraints.put(objectStructure.getOldMemoryAddress(), constraintsList.toArray(new Constraint[constraintsList.size()])); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a list of constraints of the feature's constraints. The order of constraints is important. |
||||||
|
* @param ownerOMA |
||||||
|
* the owner's old memory address |
||||||
|
* @return a table of constraints for the feature specified by old memory address |
||||||
|
*/ |
||||||
|
public Constraint[] getConstraints(Long ownerOMA) { |
||||||
|
return constraints.get(ownerOMA); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void clearState() { |
||||||
|
constraints.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The purpose of this class is to imitate bone's movement when calculating inverse kinematics. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
private static class CalculationBone extends Node { |
||||||
|
/** The name of the bone. Only to be used in toString method. */ |
||||||
|
private String boneName; |
||||||
|
/** The bone's tracks. Will be altered at the end of calculation process. */ |
||||||
|
private BoneTrack track; |
||||||
|
/** The starting position of the bone. */ |
||||||
|
private Vector3f startTranslation; |
||||||
|
/** The starting rotation of the bone. */ |
||||||
|
private Quaternion startRotation; |
||||||
|
/** The starting scale of the bone. */ |
||||||
|
private Vector3f startScale; |
||||||
|
|
||||||
|
private Vector3f[] translations; |
||||||
|
private Quaternion[] rotations; |
||||||
|
private Vector3f[] scales; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Stores the track, starting transformation and sets the transformation to the starting positions. |
||||||
|
* @param bone |
||||||
|
* the bone this class will imitate |
||||||
|
* @param track |
||||||
|
* the bone's tracks |
||||||
|
*/ |
||||||
|
public CalculationBone(Bone bone, BoneTrack track) { |
||||||
|
this.boneName = bone.getName(); |
||||||
|
this.track = track; |
||||||
|
this.startRotation = bone.getModelSpaceRotation().clone(); |
||||||
|
this.startTranslation = bone.getModelSpacePosition().clone(); |
||||||
|
this.startScale = bone.getModelSpaceScale().clone(); |
||||||
|
this.translations = track.getTranslations(); |
||||||
|
this.rotations = track.getRotations(); |
||||||
|
this.scales = track.getScales(); |
||||||
|
this.reset(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the end point of the bone. If the bone has parent it is calculated from the start point |
||||||
|
* of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered |
||||||
|
* to be 1 point up along Y axis (scale is applied if set to != 1.0); |
||||||
|
* @return the end point of this bone |
||||||
|
*/ |
||||||
|
//TODO: set to Z axis if user defined it this way
|
||||||
|
public Vector3f getEndPoint() { |
||||||
|
if(this.getParent() == null) { |
||||||
|
return new Vector3f(0, this.getLocalScale().y, 0); |
||||||
|
} else { |
||||||
|
Node parent = this.getParent(); |
||||||
|
return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method resets the calculation bone to the starting position. |
||||||
|
*/ |
||||||
|
public void reset() { |
||||||
|
this.setLocalTranslation(startTranslation); |
||||||
|
this.setLocalRotation(startRotation); |
||||||
|
this.setLocalScale(startScale); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int attachChild(Spatial child) { |
||||||
|
if(this.getChildren() != null && this.getChildren().size() > 1) { |
||||||
|
throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!"); |
||||||
|
} |
||||||
|
return super.attachChild(child); |
||||||
|
} |
||||||
|
|
||||||
|
public Spatial rotate(Quaternion rot, int frame) { |
||||||
|
Spatial spatial = super.rotate(rot); |
||||||
|
this.updateWorldTransforms(); |
||||||
|
if(this.getChildren()!=null && this.getChildren().size()>0) { |
||||||
|
CalculationBone child = (CalculationBone)this.getChild(0); |
||||||
|
child.updateWorldTransforms(); |
||||||
|
} |
||||||
|
rotations[frame].set(this.getLocalRotation()); |
||||||
|
translations[frame].set(this.getLocalTranslation()); |
||||||
|
if(scales!=null) { |
||||||
|
scales[frame].set(this.getLocalScale()); |
||||||
|
} |
||||||
|
return spatial; |
||||||
|
} |
||||||
|
|
||||||
|
public void applyCalculatedTracks() { |
||||||
|
track.setKeyframes(track.getTimes(), translations, rotations);//TODO:scales
|
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return boneName+ ": " + this.getLocalRotation() + " " + this.getLocalTranslation(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,590 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.nio.FloatBuffer; |
||||||
|
import java.nio.IntBuffer; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.material.RenderState.FaceCullMode; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Spline; |
||||||
|
import com.jme3.math.Spline.SplineType; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.math.Vector4f; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.VertexBuffer.Type; |
||||||
|
import com.jme3.scene.plugins.blender.data.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.structures.BezierCurve; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.BlenderInputStream; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
import com.jme3.scene.shape.Curve; |
||||||
|
import com.jme3.scene.shape.Surface; |
||||||
|
import com.jme3.util.BufferUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that is used in mesh calculations. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class CurvesHelper extends AbstractBlenderHelper { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName()); |
||||||
|
|
||||||
|
/** This variable indicates if the Y asxis is the UP axis or not. */ |
||||||
|
protected boolean fixUpAxis; |
||||||
|
/** Minimum basis U function degree for NURBS curves and surfaces. */ |
||||||
|
protected int minimumBasisUFunctionDegree = 4; |
||||||
|
/** Minimum basis V function degree for NURBS curves and surfaces. */ |
||||||
|
protected int minimumBasisVFunctionDegree = 4; |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in |
||||||
|
* different blender versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public CurvesHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the Y is UP axis. By default the UP axis is Z (just like in blender). |
||||||
|
* @param fixUpAxis |
||||||
|
* a variable that indicates if the Y asxis is the UP axis or not |
||||||
|
*/ |
||||||
|
public void setyIsUpAxis(boolean fixUpAxis) { |
||||||
|
this.fixUpAxis = fixUpAxis; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method converts given curve structure into a list of geometries representing the curve. The list is used here because on object |
||||||
|
* can have several separate curves. |
||||||
|
* @param curveStructure |
||||||
|
* the curve structure |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return a list of geometries repreenting a single curve object |
||||||
|
* @throws BlenderFileException |
||||||
|
*/ |
||||||
|
public List<Geometry> toCurve(Structure curveStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
String name = curveStructure.getName(); |
||||||
|
int flag = ((Number)curveStructure.getFieldValue("flag")).intValue(); |
||||||
|
boolean is3D = (flag & 0x01) != 0; |
||||||
|
boolean isFront = (flag & 0x02) != 0 && !is3D; |
||||||
|
boolean isBack = (flag & 0x04) != 0 && !is3D; |
||||||
|
if(isFront) { |
||||||
|
LOGGER.warning("No front face in curve implemented yet!");//TODO: implement front face
|
||||||
|
} |
||||||
|
if(isBack) { |
||||||
|
LOGGER.warning("No back face in curve implemented yet!");//TODO: implement back face
|
||||||
|
} |
||||||
|
|
||||||
|
//reading nurbs (and sorting them by material)
|
||||||
|
List<Structure> nurbStructures = ((Structure)curveStructure.getFieldValue("nurb")).evaluateListBase(dataRepository); |
||||||
|
Map<Number, List<Structure>> nurbs = new HashMap<Number, List<Structure>>(); |
||||||
|
for(Structure nurb : nurbStructures) { |
||||||
|
Number matNumber = (Number) nurb.getFieldValue("mat_nr"); |
||||||
|
List<Structure> nurbList = nurbs.get(matNumber); |
||||||
|
if(nurbList==null) { |
||||||
|
nurbList = new ArrayList<Structure>(); |
||||||
|
nurbs.put(matNumber, nurbList); |
||||||
|
} |
||||||
|
nurbList.add(nurb); |
||||||
|
} |
||||||
|
|
||||||
|
//getting materials
|
||||||
|
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); |
||||||
|
Material[] materials = materialHelper.getMaterials(curveStructure, dataRepository); |
||||||
|
if(materials==null) { |
||||||
|
materials = new Material[] { dataRepository.getDefaultMaterial().clone() }; |
||||||
|
} |
||||||
|
for(Material material : materials) { |
||||||
|
material.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); |
||||||
|
} |
||||||
|
|
||||||
|
//getting or creating bevel object
|
||||||
|
List<Geometry> bevelObject = null; |
||||||
|
Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj"); |
||||||
|
if(!pBevelObject.isNull()) { |
||||||
|
Pointer pBevelStructure = (Pointer) pBevelObject.fetchData(dataRepository.getInputStream()).get(0).getFieldValue("data"); |
||||||
|
Structure bevelStructure = pBevelStructure.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
bevelObject = this.toCurve(bevelStructure, dataRepository); |
||||||
|
} else { |
||||||
|
int bevResol = ((Number)curveStructure.getFieldValue("bevresol")).intValue(); |
||||||
|
float extrude = ((Number)curveStructure.getFieldValue("ext1")).floatValue(); |
||||||
|
float bevelDepth = ((Number)curveStructure.getFieldValue("ext2")).floatValue(); |
||||||
|
if(bevelDepth>0.0f) { |
||||||
|
float handlerLength = bevelDepth/2.0f; |
||||||
|
|
||||||
|
List<Vector3f> conrtolPoints = new ArrayList<Vector3f>(extrude>0.0f ? 19 : 13); |
||||||
|
conrtolPoints.add(new Vector3f(-bevelDepth,extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(-bevelDepth,handlerLength+extrude,0)); |
||||||
|
|
||||||
|
conrtolPoints.add(new Vector3f(-handlerLength,bevelDepth+extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(0,bevelDepth+extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(handlerLength,bevelDepth+extrude,0)); |
||||||
|
|
||||||
|
conrtolPoints.add(new Vector3f(bevelDepth,extrude + handlerLength,0)); |
||||||
|
conrtolPoints.add(new Vector3f(bevelDepth,extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(bevelDepth,extrude - handlerLength,0)); |
||||||
|
|
||||||
|
if(extrude>0.0f) { |
||||||
|
conrtolPoints.add(new Vector3f(bevelDepth,-extrude + handlerLength,0)); |
||||||
|
conrtolPoints.add(new Vector3f(bevelDepth,-extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(bevelDepth,-extrude-handlerLength,0)); |
||||||
|
} |
||||||
|
|
||||||
|
conrtolPoints.add(new Vector3f(handlerLength,-bevelDepth-extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(0,-bevelDepth-extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(-handlerLength,-bevelDepth-extrude,0)); |
||||||
|
|
||||||
|
conrtolPoints.add(new Vector3f(-bevelDepth,-handlerLength - extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(-bevelDepth,-extrude,0)); |
||||||
|
|
||||||
|
if(extrude>0.0f) { |
||||||
|
conrtolPoints.add(new Vector3f(-bevelDepth,handlerLength - extrude,0)); |
||||||
|
|
||||||
|
conrtolPoints.add(new Vector3f(-bevelDepth,-handlerLength + extrude,0)); |
||||||
|
conrtolPoints.add(new Vector3f(-bevelDepth,extrude,0)); |
||||||
|
} |
||||||
|
|
||||||
|
Spline bevelSpline = new Spline(SplineType.Bezier, conrtolPoints, 0, false); |
||||||
|
Curve bevelCurve = new Curve(bevelSpline, bevResol); |
||||||
|
bevelObject = new ArrayList<Geometry>(1); |
||||||
|
bevelObject.add(new Geometry("", bevelCurve)); |
||||||
|
} else if(extrude>0.0f) { |
||||||
|
Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[] { |
||||||
|
new Vector3f(0, extrude, 0), new Vector3f(0, -extrude, 0) |
||||||
|
}, 1, false); |
||||||
|
Curve bevelCurve = new Curve(bevelSpline, bevResol); |
||||||
|
bevelObject = new ArrayList<Geometry>(1); |
||||||
|
bevelObject.add(new Geometry("", bevelCurve)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//getting taper object
|
||||||
|
Curve taperObject = null; |
||||||
|
Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj"); |
||||||
|
if(bevelObject!=null && !pTaperObject.isNull()) { |
||||||
|
Pointer pTaperStructure = (Pointer) pTaperObject.fetchData(dataRepository.getInputStream()).get(0).getFieldValue("data"); |
||||||
|
Structure taperStructure = pTaperStructure.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
taperObject = this.loadTaperObject(taperStructure, dataRepository); |
||||||
|
} |
||||||
|
|
||||||
|
Vector3f loc = this.getLoc(curveStructure); |
||||||
|
//creating the result curves
|
||||||
|
List<Geometry> result = new ArrayList<Geometry>(nurbs.size()); |
||||||
|
for(Entry<Number, List<Structure>> nurbEntry : nurbs.entrySet()) { |
||||||
|
for(Structure nurb : nurbEntry.getValue()) { |
||||||
|
int type = ((Number)nurb.getFieldValue("type")).intValue(); |
||||||
|
List<Geometry> nurbGeoms = null; |
||||||
|
if((type & 0x01)!=0) {//Bezier curve
|
||||||
|
nurbGeoms = this.loadBezierCurve(loc, nurb, bevelObject, taperObject, dataRepository); |
||||||
|
} else if((type & 0x04)!=0) {//NURBS
|
||||||
|
nurbGeoms = this.loadNurb(loc, nurb, bevelObject, taperObject, dataRepository); |
||||||
|
} |
||||||
|
if(nurbGeoms!=null) {//setting the name and assigning materials
|
||||||
|
for(Geometry nurbGeom : nurbGeoms) { |
||||||
|
nurbGeom.setMaterial(materials[nurbEntry.getKey().intValue()]); |
||||||
|
nurbGeom.setName(name); |
||||||
|
result.add(nurbGeom); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method loads the bezier curve. |
||||||
|
* @param loc |
||||||
|
* the translation of the curve |
||||||
|
* @param nurb |
||||||
|
* the nurb structure |
||||||
|
* @param bevelObject |
||||||
|
* the bevel object |
||||||
|
* @param taperObject |
||||||
|
* the taper object |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return a list of geometries representing the curves |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when there are problems with the blender file |
||||||
|
*/ |
||||||
|
protected List<Geometry> loadBezierCurve(Vector3f loc, Structure nurb, List<Geometry> bevelObject, Curve taperObject, |
||||||
|
DataRepository dataRepository) throws BlenderFileException { |
||||||
|
Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt"); |
||||||
|
List<Geometry> result = new ArrayList<Geometry>(); |
||||||
|
if(!pBezierTriple.isNull()) { |
||||||
|
boolean smooth = (((Number)nurb.getFlatFieldValue("flag")).intValue() & 0x01) !=0; |
||||||
|
int resolution = ((Number)nurb.getFieldValue("resolu")).intValue(); |
||||||
|
boolean cyclic = (((Number)nurb.getFieldValue("flagu")).intValue() & 0x01) != 0; |
||||||
|
|
||||||
|
//creating the curve object
|
||||||
|
BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(dataRepository.getInputStream()), 3); |
||||||
|
List<Vector3f> controlPoints = bezierCurve.getControlPoints(); |
||||||
|
if(cyclic) { |
||||||
|
//copy the first three points at the end
|
||||||
|
for(int i=0;i<3;++i) { |
||||||
|
controlPoints.add(controlPoints.get(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
//removing the first and last handles
|
||||||
|
controlPoints.remove(0); |
||||||
|
controlPoints.remove(controlPoints.size()-1); |
||||||
|
|
||||||
|
//creating curve
|
||||||
|
Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false); |
||||||
|
Curve curve = new Curve(spline, resolution); |
||||||
|
if(bevelObject==null) {//creating a normal curve
|
||||||
|
Geometry curveGeometry = new Geometry(null, curve); |
||||||
|
result.add(curveGeometry); |
||||||
|
//TODO: use front and back flags; surface excluding algorithm for bezier circles should be added
|
||||||
|
} else {//creating curve with bevel and taper shape
|
||||||
|
result = this.applyBevelAndTaper(curve, bevelObject, taperObject, smooth, dataRepository); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method loads the NURBS curve or surface. |
||||||
|
* @param loc |
||||||
|
* object's location |
||||||
|
* @param nurb |
||||||
|
* the NURBS data structure |
||||||
|
* @param bevelObject |
||||||
|
* the bevel object to be applied |
||||||
|
* @param taperObject |
||||||
|
* the taper object to be applied |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return a list of geometries that represents the loaded NURBS curve or surface |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is throw when problems with blender loaded data occurs |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
protected List<Geometry> loadNurb(Vector3f loc, Structure nurb, List<Geometry> bevelObject, Curve taperObject, |
||||||
|
DataRepository dataRepository) throws BlenderFileException { |
||||||
|
//loading the knots
|
||||||
|
List<Float>[] knots = new List[2]; |
||||||
|
Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") }; |
||||||
|
for(int i=0;i<knots.length; ++i) { |
||||||
|
if(!pKnots[i].isNull()) { |
||||||
|
FileBlockHeader fileBlockHeader = dataRepository.getFileBlock(pKnots[i].getOldMemoryAddress()); |
||||||
|
BlenderInputStream blenderInputStream = dataRepository.getInputStream(); |
||||||
|
blenderInputStream.setPosition(fileBlockHeader.getBlockPosition()); |
||||||
|
int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4; |
||||||
|
knots[i] = new ArrayList<Float>(knotsAmount); |
||||||
|
for(int j=0;j<knotsAmount;++j) { |
||||||
|
knots[i].add(Float.valueOf(blenderInputStream.readFloat())); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//loading the flags and orders (basis functions degrees)
|
||||||
|
int flagU = ((Number)nurb.getFieldValue("flagu")).intValue(); |
||||||
|
int flagV = ((Number)nurb.getFieldValue("flagv")).intValue(); |
||||||
|
int orderU = ((Number)nurb.getFieldValue("orderu")).intValue(); |
||||||
|
int orderV = ((Number)nurb.getFieldValue("orderv")).intValue(); |
||||||
|
|
||||||
|
//loading control points and their weights
|
||||||
|
int pntsU = ((Number)nurb.getFieldValue("pntsu")).intValue(); |
||||||
|
int pntsV = ((Number)nurb.getFieldValue("pntsv")).intValue(); |
||||||
|
List<Structure> bPoints = ((Pointer)nurb.getFieldValue("bp")).fetchData(dataRepository.getInputStream()); |
||||||
|
List<List<Vector4f>> controlPoints = new ArrayList<List<Vector4f>>(pntsV); |
||||||
|
for(int i=0;i<pntsV;++i) { |
||||||
|
List<Vector4f> uControlPoints = new ArrayList<Vector4f>(pntsU); |
||||||
|
for(int j=0;j<pntsU;++j) { |
||||||
|
DynamicArray<Float> vec = (DynamicArray<Float>)bPoints.get(j + i*pntsU).getFieldValue("vec"); |
||||||
|
if(fixUpAxis) { |
||||||
|
uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue())); |
||||||
|
} else { |
||||||
|
uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue())); |
||||||
|
} |
||||||
|
} |
||||||
|
if((flagU & 0x01) != 0) { |
||||||
|
for(int k=0;k<orderU - 1;++k) { |
||||||
|
uControlPoints.add(uControlPoints.get(k)); |
||||||
|
} |
||||||
|
} |
||||||
|
controlPoints.add(uControlPoints); |
||||||
|
} |
||||||
|
if((flagV & 0x01) != 0) { |
||||||
|
for(int k=0;k<orderV - 1;++k) { |
||||||
|
controlPoints.add(controlPoints.get(k)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int resolu = ((Number)nurb.getFieldValue("resolu")).intValue() + 1; |
||||||
|
List<Geometry> result; |
||||||
|
if(knots[1]==null) {//creating the curve
|
||||||
|
Spline nurbSpline = new Spline(controlPoints.get(0), knots[0]); |
||||||
|
Curve nurbCurve = new Curve(nurbSpline, resolu); |
||||||
|
if(bevelObject!=null) { |
||||||
|
result = this.applyBevelAndTaper(nurbCurve, bevelObject, taperObject, true, dataRepository);//TODO: smooth
|
||||||
|
} else { |
||||||
|
result = new ArrayList<Geometry>(1); |
||||||
|
Geometry nurbGeometry = new Geometry("", nurbCurve); |
||||||
|
result.add(nurbGeometry); |
||||||
|
} |
||||||
|
} else {//creating the nurb surface
|
||||||
|
int resolv = ((Number)nurb.getFieldValue("resolv")).intValue() + 1; |
||||||
|
Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, resolu, resolv, orderU, orderV); |
||||||
|
Geometry nurbGeometry = new Geometry("", nurbSurface); |
||||||
|
result = new ArrayList<Geometry>(1); |
||||||
|
result.add(nurbGeometry); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the taper scale that should be applied to the object. |
||||||
|
* @param taperPoints |
||||||
|
* the taper points |
||||||
|
* @param taperLength |
||||||
|
* the taper curve length |
||||||
|
* @param percent |
||||||
|
* the percent of way along the whole taper curve |
||||||
|
* @param store |
||||||
|
* the vector where the result will be stored |
||||||
|
*/ |
||||||
|
protected float getTaperScale(float[] taperPoints, float taperLength, float percent) { |
||||||
|
float length = taperLength * percent; |
||||||
|
float currentLength = 0; |
||||||
|
Vector3f p = new Vector3f(); |
||||||
|
int i; |
||||||
|
for(i=0;i<taperPoints.length-6 && currentLength < length; i += 3) { |
||||||
|
p.set(taperPoints[i], taperPoints[i+1], taperPoints[i+2]); |
||||||
|
p.subtractLocal(taperPoints[i+3], taperPoints[i+4], taperPoints[i+5]); |
||||||
|
currentLength += p.length(); |
||||||
|
} |
||||||
|
currentLength -= p.length(); |
||||||
|
float leftLength = length - currentLength; |
||||||
|
float percentOnSegment = p.length()==0 ? 0 : leftLength / p.length(); |
||||||
|
Vector3f store = FastMath.interpolateLinear(percentOnSegment, |
||||||
|
new Vector3f(taperPoints[i], taperPoints[i+1], taperPoints[i+2]), |
||||||
|
new Vector3f(taperPoints[i+3], taperPoints[i+4], taperPoints[i+5])); |
||||||
|
return store.y; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method applies bevel and taper objects to the curve. |
||||||
|
* @param curve |
||||||
|
* the curve we apply the objects to |
||||||
|
* @param bevelObject |
||||||
|
* the bevel object |
||||||
|
* @param taperObject |
||||||
|
* the taper object |
||||||
|
* @param smooth |
||||||
|
* the smooth flag |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return a list of geometries representing the beveled and/or tapered curve |
||||||
|
*/ |
||||||
|
protected List<Geometry> applyBevelAndTaper(Curve curve, List<Geometry> bevelObject, Curve taperObject, |
||||||
|
boolean smooth, DataRepository dataRepository) { |
||||||
|
float[] curvePoints = BufferUtils.getFloatArray(curve.getFloatBuffer(Type.Position)); |
||||||
|
MeshHelper meshHelper = dataRepository.getHelper(MeshHelper.class); |
||||||
|
float curveLength = curve.getLength(); |
||||||
|
//TODO: use the smooth var
|
||||||
|
|
||||||
|
//taper data
|
||||||
|
float[] taperPoints = null; |
||||||
|
float taperLength = 0; |
||||||
|
if(taperObject!=null) { |
||||||
|
taperPoints = BufferUtils.getFloatArray(taperObject.getFloatBuffer(Type.Position)); |
||||||
|
taperLength = taperObject.getLength(); |
||||||
|
} |
||||||
|
|
||||||
|
//several objects can be allocated only once
|
||||||
|
Vector3f p = new Vector3f(); |
||||||
|
Vector3f z = new Vector3f(0,0,1); |
||||||
|
Vector3f negativeY = new Vector3f(0, -1, 0); |
||||||
|
Matrix4f m = new Matrix4f(); |
||||||
|
float lengthAlongCurve = 0, taperScale = 1.0f; |
||||||
|
Quaternion planeRotation = new Quaternion(); |
||||||
|
Quaternion zRotation = new Quaternion(); |
||||||
|
float[] temp = new float[] {0,0,0,1}; |
||||||
|
Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>();//normalMap merges normals of faces that will be rendered smooth
|
||||||
|
|
||||||
|
FloatBuffer[] vertexBuffers = new FloatBuffer[bevelObject.size()]; |
||||||
|
FloatBuffer[] normalBuffers = new FloatBuffer[bevelObject.size()]; |
||||||
|
IntBuffer[] indexBuffers = new IntBuffer[bevelObject.size()]; |
||||||
|
for(int geomIndex = 0;geomIndex<bevelObject.size();++geomIndex) { |
||||||
|
Mesh mesh = bevelObject.get(geomIndex).getMesh(); |
||||||
|
FloatBuffer positions = mesh.getFloatBuffer(Type.Position); |
||||||
|
float[] vertices = BufferUtils.getFloatArray(positions); |
||||||
|
|
||||||
|
for(int i=0;i<curvePoints.length;i+=3) { |
||||||
|
p.set(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); |
||||||
|
Vector3f v; |
||||||
|
if(i==0) { |
||||||
|
v = new Vector3f(curvePoints[3] - p.x, curvePoints[4] - p.y, curvePoints[5] - p.z); |
||||||
|
} else if(i+3>=curvePoints.length) { |
||||||
|
v = new Vector3f(p.x - curvePoints[i-3], p.y - curvePoints[i-2], p.z - curvePoints[i-1]); |
||||||
|
lengthAlongCurve += v.length(); |
||||||
|
} else { |
||||||
|
v = new Vector3f(curvePoints[i+3] - curvePoints[i-3], |
||||||
|
curvePoints[i+4] - curvePoints[i-2], |
||||||
|
curvePoints[i+5] - curvePoints[i-1]); |
||||||
|
lengthAlongCurve += new Vector3f(curvePoints[i+3] - p.x, curvePoints[i+4] - p.y, curvePoints[i+5] - p.z).length(); |
||||||
|
} |
||||||
|
v.normalizeLocal(); |
||||||
|
|
||||||
|
float angle = FastMath.acos(v.dot(z)); |
||||||
|
v.crossLocal(z).normalizeLocal();//v is the rotation axis now
|
||||||
|
planeRotation.fromAngleAxis(angle, v); |
||||||
|
|
||||||
|
Vector3f zAxisRotationVector = negativeY.cross(v).normalizeLocal(); |
||||||
|
float zAxisRotationAngle = FastMath.acos(negativeY.dot(v)); |
||||||
|
zRotation.fromAngleAxis(zAxisRotationAngle, zAxisRotationVector); |
||||||
|
|
||||||
|
//point transformation matrix
|
||||||
|
if(taperPoints!=null) { |
||||||
|
taperScale = this.getTaperScale(taperPoints, taperLength, lengthAlongCurve / curveLength); |
||||||
|
} |
||||||
|
m.set(Matrix4f.IDENTITY); |
||||||
|
m.setRotationQuaternion(planeRotation.multLocal(zRotation)); |
||||||
|
m.setTranslation(p); |
||||||
|
|
||||||
|
//these vertices need to be thrown on XY plane
|
||||||
|
//and moved to the origin of [p1.x, p1.y] on the plane
|
||||||
|
Vector3f[] verts = new Vector3f[vertices.length/3]; |
||||||
|
for(int j=0;j<verts.length;++j) { |
||||||
|
temp[0] = vertices[j*3] * taperScale; |
||||||
|
temp[1] = vertices[j*3+1] * taperScale; |
||||||
|
temp[2] = 0; |
||||||
|
m.mult(temp);//the result is stored in the array
|
||||||
|
if(fixUpAxis) { |
||||||
|
verts[j] = new Vector3f(temp[0], temp[1], temp[2]); |
||||||
|
} else { |
||||||
|
verts[j] = new Vector3f(temp[0], temp[2], -temp[1]); |
||||||
|
} |
||||||
|
} |
||||||
|
if(vertexBuffers[geomIndex]==null) { |
||||||
|
vertexBuffers[geomIndex] = BufferUtils.createFloatBuffer(verts.length * curvePoints.length); |
||||||
|
} |
||||||
|
FloatBuffer buffer = BufferUtils.createFloatBuffer(verts); |
||||||
|
vertexBuffers[geomIndex].put(buffer); |
||||||
|
|
||||||
|
//adding indexes
|
||||||
|
IntBuffer indexBuffer = indexBuffers[geomIndex]; |
||||||
|
if(indexBuffer==null) { |
||||||
|
//the amount of faces in the final mesh is the amount of edges in the bevel curve
|
||||||
|
//(which is less by 1 than its number of vertices)
|
||||||
|
//multiplied by 2 (because each edge has two faces assigned on both sides)
|
||||||
|
//and multiplied by the amount of bevel curve repeats which is equal to the amount of vertices on the target curve
|
||||||
|
//finally we need to subtract the bevel edges amount 2 times because the border edges have only one face attached
|
||||||
|
//and at last multiply everything by 3 because each face needs 3 indexes to be described
|
||||||
|
int bevelCurveEdgesAmount = verts.length-1; |
||||||
|
indexBuffer = BufferUtils.createIntBuffer(((bevelCurveEdgesAmount << 1) * curvePoints.length - bevelCurveEdgesAmount << 1) * 3); |
||||||
|
indexBuffers[geomIndex] = indexBuffer; |
||||||
|
} |
||||||
|
int pointOffset = i/3 * verts.length; |
||||||
|
if(i+3<curvePoints.length) { |
||||||
|
for(int index=0;index<verts.length-1;++index) { |
||||||
|
indexBuffer.put(index + pointOffset); |
||||||
|
indexBuffer.put(index + pointOffset + 1); |
||||||
|
indexBuffer.put(verts.length + index + pointOffset); |
||||||
|
indexBuffer.put(verts.length + index + pointOffset); |
||||||
|
indexBuffer.put(index + pointOffset + 1); |
||||||
|
indexBuffer.put(verts.length + index + pointOffset + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//calculating the normals
|
||||||
|
for(int geomIndex = 0;geomIndex<bevelObject.size();++geomIndex) { |
||||||
|
Vector3f[] allVerts = BufferUtils.getVector3Array(vertexBuffers[geomIndex]); |
||||||
|
int[] allIndices = BufferUtils.getIntArray(indexBuffers[geomIndex]); |
||||||
|
for(int i=0;i<allIndices.length-3;i+=3) { |
||||||
|
Vector3f n = FastMath.computeNormal(allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]); |
||||||
|
meshHelper.addNormal(n, normalMap, smooth, allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]); |
||||||
|
} |
||||||
|
if(normalBuffers[geomIndex]==null) { |
||||||
|
normalBuffers[geomIndex] = BufferUtils.createFloatBuffer(allVerts.length * 3); |
||||||
|
} |
||||||
|
for(Vector3f v : allVerts) { |
||||||
|
Vector3f n = normalMap.get(v); |
||||||
|
normalBuffers[geomIndex].put(n.x); |
||||||
|
normalBuffers[geomIndex].put(n.y); |
||||||
|
normalBuffers[geomIndex].put(n.z); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
List<Geometry> result = new ArrayList<Geometry>(vertexBuffers.length); |
||||||
|
for(int i=0;i<vertexBuffers.length;++i) { |
||||||
|
Mesh mesh = new Mesh(); |
||||||
|
mesh.setBuffer(Type.Position, 3, vertexBuffers[i]); |
||||||
|
mesh.setBuffer(Type.Index, 3, indexBuffers[i]); |
||||||
|
mesh.setBuffer(Type.Normal, 3, normalBuffers[i]); |
||||||
|
Geometry g = new Geometry("g" + i, mesh); |
||||||
|
g.updateModelBound(); |
||||||
|
result.add(g); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method loads the taper object. |
||||||
|
* @param taperStructure |
||||||
|
* the taper structure |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return the taper object |
||||||
|
* @throws BlenderFileException |
||||||
|
*/ |
||||||
|
protected Curve loadTaperObject(Structure taperStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
//reading nurbs
|
||||||
|
List<Structure> nurbStructures = ((Structure)taperStructure.getFieldValue("nurb")).evaluateListBase(dataRepository); |
||||||
|
for(Structure nurb : nurbStructures) { |
||||||
|
Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt"); |
||||||
|
if(!pBezierTriple.isNull()) { |
||||||
|
//creating the curve object
|
||||||
|
BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(dataRepository.getInputStream()), 3); |
||||||
|
List<Vector3f> controlPoints = bezierCurve.getControlPoints(); |
||||||
|
//removing the first and last handles
|
||||||
|
controlPoints.remove(0); |
||||||
|
controlPoints.remove(controlPoints.size()-1); |
||||||
|
|
||||||
|
//return the first taper curve that has more than 3 control points
|
||||||
|
if(controlPoints.size()>3) { |
||||||
|
Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false); |
||||||
|
int resolution = ((Number)taperStructure.getFieldValue("resolu")).intValue(); |
||||||
|
return new Curve(spline, resolution); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the translation of the curve. The UP axis is taken into account here. |
||||||
|
* @param curveStructure |
||||||
|
* the curve structure |
||||||
|
* @return curve translation |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
protected Vector3f getLoc(Structure curveStructure) { |
||||||
|
DynamicArray<Number> locArray = (DynamicArray<Number>)curveStructure.getFieldValue("loc"); |
||||||
|
if(fixUpAxis) { |
||||||
|
return new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), -locArray.get(2).floatValue()); |
||||||
|
} else { |
||||||
|
return new Vector3f(locArray.get(0).floatValue(), locArray.get(2).floatValue(), locArray.get(1).floatValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.structures.BezierCurve; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Ipo; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class helps to compute values from interpolation curves for features like animation or constraint influence. The |
||||||
|
* curves are 3rd degree bezier curves. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class IpoHelper extends AbstractBlenderHelper { |
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in |
||||||
|
* different blender versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public IpoHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method creates an ipo object used for interpolation calculations. |
||||||
|
* @param ipoStructure |
||||||
|
* the structure with ipo definition |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return the ipo object |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow corrupted |
||||||
|
*/ |
||||||
|
public Ipo createIpo(Structure ipoStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
Structure curvebase = (Structure)ipoStructure.getFieldValue("curve"); |
||||||
|
|
||||||
|
//preparing bezier curves
|
||||||
|
Ipo result = null; |
||||||
|
List<Structure> curves = curvebase.evaluateListBase(dataRepository);//IpoCurve
|
||||||
|
if(curves.size() > 0) { |
||||||
|
BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; |
||||||
|
int frame = 0; |
||||||
|
for(Structure curve : curves) { |
||||||
|
Pointer pBezTriple = (Pointer)curve.getFieldValue("bezt"); |
||||||
|
List<Structure> bezTriples = pBezTriple.fetchData(dataRepository.getInputStream()); |
||||||
|
int type = ((Number)curve.getFieldValue("adrcode")).intValue(); |
||||||
|
bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); |
||||||
|
} |
||||||
|
curves.clear(); |
||||||
|
result = new Ipo(bezierCurves); |
||||||
|
dataRepository.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method creates an ipo with only a single value. No track type is specified so do not use it for calculating |
||||||
|
* tracks. |
||||||
|
* @param constValue |
||||||
|
* the value of this ipo |
||||||
|
* @return constant ipo |
||||||
|
*/ |
||||||
|
public Ipo createIpo(float constValue) { |
||||||
|
return new ConstIpo(constValue); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Ipo constant curve. This is a curve with only one value and no specified type. This type of ipo cannot be used to |
||||||
|
* calculate tracks. It should only be used to calculate single value for a given frame. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
private class ConstIpo extends Ipo { |
||||||
|
/** The constant value of this ipo. */ |
||||||
|
private float constValue; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Stores the constant value of this ipo. |
||||||
|
* @param constValue |
||||||
|
* the constant value of this ipo |
||||||
|
*/ |
||||||
|
public ConstIpo(float constValue) { |
||||||
|
super(null); |
||||||
|
this.constValue = constValue; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public float calculateValue(int frame) { |
||||||
|
return constValue; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public float calculateValue(int frame, int curveIndex) { |
||||||
|
return constValue; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getCurvesAmount() { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BoneTrack calculateTrack(int boneIndex, int startFrame, int stopFrame, int fps) { |
||||||
|
throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,589 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.asset.BlenderKey.FeaturesToLoad; |
||||||
|
import com.jme3.material.MatParam; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.material.Material.MatParamTexture; |
||||||
|
import com.jme3.material.RenderState.BlendMode; |
||||||
|
import com.jme3.material.RenderState.FaceCullMode; |
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
import com.jme3.shader.VarType; |
||||||
|
import com.jme3.texture.Texture; |
||||||
|
import com.jme3.texture.Texture.WrapMode; |
||||||
|
|
||||||
|
public class MaterialHelper extends AbstractBlenderHelper { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName()); |
||||||
|
protected static final float DEFAULT_SHININESS = 20.0f; |
||||||
|
|
||||||
|
public static final String TEXTURE_TYPE_COLOR = "ColorMap"; |
||||||
|
public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap"; |
||||||
|
public static final String TEXTURE_TYPE_NORMAL = "NormalMap"; |
||||||
|
public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap"; |
||||||
|
public static final String TEXTURE_TYPE_GLOW = "GlowMap"; |
||||||
|
public static final String TEXTURE_TYPE_ALPHA = "AlphaMap"; |
||||||
|
|
||||||
|
/** |
||||||
|
* The type of the material's diffuse shader. |
||||||
|
*/ |
||||||
|
public static enum DiffuseShader { |
||||||
|
LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The type of the material's specular shader. |
||||||
|
*/ |
||||||
|
public static enum SpecularShader { |
||||||
|
COOKTORRENCE, PHONG, BLINN, TOON, WARDISO |
||||||
|
} |
||||||
|
|
||||||
|
/** Face cull mode. Should be excplicitly set before this helper is used. */ |
||||||
|
protected FaceCullMode faceCullMode; |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender |
||||||
|
* versions. |
||||||
|
* |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public MaterialHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the face cull mode to be used with every loaded material. |
||||||
|
* |
||||||
|
* @param faceCullMode |
||||||
|
* the face cull mode |
||||||
|
*/ |
||||||
|
public void setFaceCullMode(FaceCullMode faceCullMode) { |
||||||
|
this.faceCullMode = faceCullMode; |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public Material toMaterial(Structure structure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
LOGGER.log(Level.INFO, "Loading material."); |
||||||
|
if (structure == null) { |
||||||
|
return dataRepository.getDefaultMaterial(); |
||||||
|
} |
||||||
|
Material result = (Material) dataRepository.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
if (result != null) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
int mode = ((Number) structure.getFieldValue("mode")).intValue(); |
||||||
|
boolean shadeless = (mode & 0x4) != 0; |
||||||
|
boolean vertexColor = (mode & 0x16) != 0; |
||||||
|
boolean transparent = (mode & 0x64) != 0; |
||||||
|
|
||||||
|
if (shadeless) { |
||||||
|
result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); |
||||||
|
} else { |
||||||
|
result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); |
||||||
|
} |
||||||
|
|
||||||
|
result.getAdditionalRenderState().setFaceCullMode(faceCullMode); |
||||||
|
|
||||||
|
if (transparent) { |
||||||
|
result.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); |
||||||
|
} |
||||||
|
|
||||||
|
String name = structure.getName(); |
||||||
|
LOGGER.log(Level.INFO, "Material's name: {0}", name); |
||||||
|
if (vertexColor) { |
||||||
|
if (shadeless) { |
||||||
|
result.setBoolean("VertexColor", true); |
||||||
|
} else { |
||||||
|
result.setBoolean("UseVertexColor", true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); |
||||||
|
ColorRGBA diffuseColor = null; |
||||||
|
if (shadeless) { |
||||||
|
// color of shadeless? doesn't seem to work in blender ..
|
||||||
|
} else { |
||||||
|
result.setBoolean("UseMaterialColors", true); |
||||||
|
|
||||||
|
// setting the colors
|
||||||
|
DiffuseShader diffuseShader = materialHelper.getDiffuseShader(structure); |
||||||
|
result.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT); |
||||||
|
diffuseColor = materialHelper.getDiffuseColor(structure, diffuseShader); |
||||||
|
result.setColor("Diffuse", diffuseColor); |
||||||
|
|
||||||
|
SpecularShader specularShader = materialHelper.getSpecularShader(structure); |
||||||
|
result.setBoolean("WardIso", specularShader == SpecularShader.WARDISO); |
||||||
|
result.setColor("Specular", materialHelper.getSpecularColor(structure, specularShader)); |
||||||
|
|
||||||
|
result.setColor("Ambient", materialHelper.getAmbientColor(structure)); |
||||||
|
result.setFloat("Shininess", materialHelper.getShininess(structure)); |
||||||
|
} |
||||||
|
|
||||||
|
// texture
|
||||||
|
if ((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0) { |
||||||
|
TextureHelper textureHelper = dataRepository.getHelper(TextureHelper.class); |
||||||
|
DynamicArray<Pointer> mtexs = (DynamicArray<Pointer>) structure.getFieldValue("mtex"); |
||||||
|
for (int i = 0; i < mtexs.getTotalSize(); ++i) { |
||||||
|
Pointer p = mtexs.get(i); |
||||||
|
if (!p.isNull()) { |
||||||
|
List<Structure> mtex = p.fetchData(dataRepository.getInputStream()); |
||||||
|
if (mtex.size() == 1) { |
||||||
|
Structure textureLink = mtex.get(0); |
||||||
|
int texflag = ((Number)textureLink.getFieldValue("texflag")).intValue(); |
||||||
|
//int texco = ((Number) textureLink.getFieldValue("texco")).intValue();
|
||||||
|
boolean negateTexture = (texflag & 0x04)==0; |
||||||
|
|
||||||
|
// if(texco == 0x10) {//TEXCO_UV (this is only supported now)
|
||||||
|
int mapto = ((Number) textureLink.getFieldValue("mapto")).intValue(); |
||||||
|
if (mapto != 0) { |
||||||
|
Pointer pTex = (Pointer) textureLink.getFieldValue("tex"); |
||||||
|
Structure tex = pTex.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
Texture texture = textureHelper.getTexture(tex, dataRepository); |
||||||
|
if (texture != null) { |
||||||
|
if ((mapto & 0x01) != 0) {// Col
|
||||||
|
result.setBoolean("UseMaterialColors", Boolean.FALSE); |
||||||
|
// blending the texture with material color and texture's defined color
|
||||||
|
int blendType = ((Number) textureLink.getFieldValue("blendtype")).intValue(); |
||||||
|
float[] color = new float[] { ((Number) textureLink.getFieldValue("r")).floatValue(), |
||||||
|
((Number) textureLink.getFieldValue("g")).floatValue(), |
||||||
|
((Number) textureLink.getFieldValue("b")).floatValue() }; |
||||||
|
float colfac = ((Number) textureLink.getFieldValue("colfac")).floatValue(); |
||||||
|
texture = textureHelper.blendTexture(diffuseColor.getColorArray(), texture, color, colfac, blendType, |
||||||
|
negateTexture, dataRepository); |
||||||
|
texture.setWrap(WrapMode.Repeat); |
||||||
|
if (shadeless) { |
||||||
|
result.setTexture(TEXTURE_TYPE_COLOR, texture); |
||||||
|
} else { |
||||||
|
result.setTexture(TEXTURE_TYPE_DIFFUSE, texture); |
||||||
|
} |
||||||
|
} |
||||||
|
if ((mapto & 0x02) != 0) {// Nor
|
||||||
|
result.setTexture(TEXTURE_TYPE_NORMAL, texture); |
||||||
|
} |
||||||
|
if ((mapto & 0x20) != 0) {// Spec
|
||||||
|
result.setTexture(TEXTURE_TYPE_SPECULAR, texture); |
||||||
|
} |
||||||
|
if ((mapto & 0x40) != 0) {// Emit
|
||||||
|
result.setTexture(TEXTURE_TYPE_GLOW, texture); |
||||||
|
} |
||||||
|
if ((mapto & 0x80) != 0) {// Alpha
|
||||||
|
result.setTexture(TEXTURE_TYPE_ALPHA, texture); |
||||||
|
} |
||||||
|
} else { |
||||||
|
LOGGER.log(Level.WARNING, "Texture not found!"); |
||||||
|
} |
||||||
|
} |
||||||
|
// } else {
|
||||||
|
// Pointer pTex = (Pointer)textureLink.getFieldValue("tex");
|
||||||
|
// List<Structure> texs = pTex.fetchData(dataRepository.getInputStream());
|
||||||
|
// Structure tex = texs.get(0);
|
||||||
|
// LOGGER.log(Level.WARNING, "Unsupported texture type: " + texco);
|
||||||
|
// }
|
||||||
|
} else { |
||||||
|
LOGGER.log(Level.WARNING, "Many textures. Not solved yet!");// TODO
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but |
||||||
|
* returned itself. |
||||||
|
* |
||||||
|
* @param material |
||||||
|
* a material to be cloned without textures |
||||||
|
* @param imageType |
||||||
|
* type of image defined by blender; the constants are defined in TextureHelper |
||||||
|
* @return material without textures of a specified type |
||||||
|
*/ |
||||||
|
public Material getNonTexturedMaterial(Material material, int imageType) { |
||||||
|
String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, |
||||||
|
TEXTURE_TYPE_ALPHA }; |
||||||
|
Map<String, Texture> textures = new HashMap<String, Texture>(textureParamNames.length); |
||||||
|
for (String textureParamName : textureParamNames) { |
||||||
|
MatParamTexture matParamTexture = material.getTextureParam(textureParamName); |
||||||
|
if (matParamTexture != null) { |
||||||
|
textures.put(textureParamName, matParamTexture.getTextureValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
if (textures.isEmpty()) { |
||||||
|
return material; |
||||||
|
} else { |
||||||
|
// clear all textures first so that wo de not waste resources cloning them
|
||||||
|
for (Entry<String, Texture> textureParamName : textures.entrySet()) { |
||||||
|
String name = textureParamName.getValue().getName(); |
||||||
|
try { |
||||||
|
int type = Integer.parseInt(name); |
||||||
|
if (type == imageType) { |
||||||
|
material.clearParam(textureParamName.getKey()); |
||||||
|
} |
||||||
|
} catch (NumberFormatException e) { |
||||||
|
LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", |
||||||
|
name); |
||||||
|
} |
||||||
|
} |
||||||
|
Material result = material.clone(); |
||||||
|
// put the textures back in place
|
||||||
|
for (Entry<String, Texture> textureEntry : textures.entrySet()) { |
||||||
|
material.setTexture(textureEntry.getKey(), textureEntry.getValue()); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method converts the given material into particles-usable material. |
||||||
|
* The texture and glow color are being copied. |
||||||
|
* The method assumes it receives the Lighting type of material. |
||||||
|
* @param material the source material |
||||||
|
* @param dataRepository the data repository |
||||||
|
* @return material converted into particles-usable material |
||||||
|
*/ |
||||||
|
public Material getParticlesMaterial(Material material, DataRepository dataRepository) { |
||||||
|
Material result = new Material(dataRepository.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); |
||||||
|
|
||||||
|
//copying texture
|
||||||
|
MatParam diffuseMap = material.getParam("DiffuseMap"); |
||||||
|
if(diffuseMap!=null) { |
||||||
|
Texture texture = (Texture) diffuseMap.getValue(); |
||||||
|
result.setTextureParam("Texture", VarType.Texture2D, texture); |
||||||
|
} |
||||||
|
|
||||||
|
//copying glow color
|
||||||
|
MatParam glowColor = material.getParam("GlowColor"); |
||||||
|
if(glowColor!=null) { |
||||||
|
ColorRGBA color = (ColorRGBA) glowColor.getValue(); |
||||||
|
result.setParam("GlowColor", VarType.Vector3, color); |
||||||
|
} |
||||||
|
//material.setTexture("Texture", dataRepository.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method indicates if the material has a texture of a specified type. |
||||||
|
* |
||||||
|
* @param material |
||||||
|
* the material |
||||||
|
* @param textureType |
||||||
|
* the type of the texture |
||||||
|
* @return <b>true</b> if the texture exists in the material and <B>false</b> otherwise |
||||||
|
*/ |
||||||
|
public boolean hasTexture(Material material, String textureType) { |
||||||
|
if (material != null) { |
||||||
|
return material.getTextureParam(textureType) != null; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the diffuse color |
||||||
|
* |
||||||
|
* @param materialStructure |
||||||
|
* @param diffuseShader |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public ColorRGBA getDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) { |
||||||
|
// bitwise 'or' of all textures mappings
|
||||||
|
int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue(); |
||||||
|
|
||||||
|
// diffuse color
|
||||||
|
float r = ((Number) materialStructure.getFieldValue("r")).floatValue(); |
||||||
|
float g = ((Number) materialStructure.getFieldValue("g")).floatValue(); |
||||||
|
float b = ((Number) materialStructure.getFieldValue("b")).floatValue(); |
||||||
|
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); |
||||||
|
if ((commonMapto & 0x01) == 0x01) {// Col
|
||||||
|
return new ColorRGBA(r, g, b, alpha); |
||||||
|
} else { |
||||||
|
switch (diffuseShader) { |
||||||
|
case FRESNEL: |
||||||
|
case ORENNAYAR: |
||||||
|
case TOON: |
||||||
|
break;// TODO: find what is the proper modification
|
||||||
|
case MINNAERT: |
||||||
|
case LAMBERT:// TODO: check if that is correct
|
||||||
|
float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue(); |
||||||
|
r *= ref; |
||||||
|
g *= ref; |
||||||
|
b *= ref; |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString()); |
||||||
|
} |
||||||
|
return new ColorRGBA(r, g, b, alpha); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns an enum describing the type of a diffuse shader used by this material. |
||||||
|
* |
||||||
|
* @param materialStructure |
||||||
|
* the material structure filled with data |
||||||
|
* @return an enum describing the type of a diffuse shader used by this material |
||||||
|
*/ |
||||||
|
public DiffuseShader getDiffuseShader(Structure materialStructure) { |
||||||
|
int diff_shader = ((Number) materialStructure.getFieldValue("diff_shader")).intValue(); |
||||||
|
return DiffuseShader.values()[diff_shader]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns an ambient color used by the material. |
||||||
|
* |
||||||
|
* @param materialStructure |
||||||
|
* the material structure filled with data |
||||||
|
* @return an ambient color used by the material |
||||||
|
*/ |
||||||
|
public ColorRGBA getAmbientColor(Structure materialStructure) { |
||||||
|
float r = ((Number) materialStructure.getFieldValue("ambr")).floatValue(); |
||||||
|
float g = ((Number) materialStructure.getFieldValue("ambg")).floatValue(); |
||||||
|
float b = ((Number) materialStructure.getFieldValue("ambb")).floatValue(); |
||||||
|
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); |
||||||
|
return new ColorRGBA(r, g, b, alpha); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns an enum describing the type of a specular shader used by this material. |
||||||
|
* |
||||||
|
* @param materialStructure |
||||||
|
* the material structure filled with data |
||||||
|
* @return an enum describing the type of a specular shader used by this material |
||||||
|
*/ |
||||||
|
public SpecularShader getSpecularShader(Structure materialStructure) { |
||||||
|
int spec_shader = ((Number) materialStructure.getFieldValue("spec_shader")).intValue(); |
||||||
|
return SpecularShader.values()[spec_shader]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a specular color used by the material. |
||||||
|
* |
||||||
|
* @param materialStructure |
||||||
|
* the material structure filled with data |
||||||
|
* @return a specular color used by the material |
||||||
|
*/ |
||||||
|
public ColorRGBA getSpecularColor(Structure materialStructure, SpecularShader specularShader) { |
||||||
|
float r = ((Number) materialStructure.getFieldValue("specr")).floatValue(); |
||||||
|
float g = ((Number) materialStructure.getFieldValue("specg")).floatValue(); |
||||||
|
float b = ((Number) materialStructure.getFieldValue("specb")).floatValue(); |
||||||
|
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); |
||||||
|
switch (specularShader) { |
||||||
|
case BLINN: |
||||||
|
case COOKTORRENCE: |
||||||
|
case TOON: |
||||||
|
case WARDISO:// TODO: find what is the proper modification
|
||||||
|
break; |
||||||
|
case PHONG:// TODO: check if that is correct
|
||||||
|
float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue(); |
||||||
|
r *= spec * 0.5f; |
||||||
|
g *= spec * 0.5f; |
||||||
|
b *= spec * 0.5f; |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString()); |
||||||
|
} |
||||||
|
return new ColorRGBA(r, g, b, alpha); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the sihiness of this material or DEFAULT_SHININESS value if not present. |
||||||
|
* |
||||||
|
* @param materialStructure |
||||||
|
* the material structure filled with data |
||||||
|
* @return the sihiness of this material or DEFAULT_SHININESS value if not present |
||||||
|
*/ |
||||||
|
public float getShininess(Structure materialStructure) { |
||||||
|
float shininess = ((Number) materialStructure.getFieldValue("emit")).floatValue(); |
||||||
|
return shininess > 0.0f ? shininess : DEFAULT_SHININESS; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or |
||||||
|
* curve) but needs to have 'mat' field/ |
||||||
|
* |
||||||
|
* @param structureWithMaterials |
||||||
|
* the structure containing the mesh data |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return a list of vertices colors, each color belongs to a single vertex |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blend file structure is somehow invalid or corrupted |
||||||
|
*/ |
||||||
|
public Material[] getMaterials(Structure structureWithMaterials, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat"); |
||||||
|
Material[] materials = null; |
||||||
|
if (!ppMaterials.isNull()) { |
||||||
|
List<Structure> materialStructures = ppMaterials.fetchData(dataRepository.getInputStream()); |
||||||
|
if (materialStructures != null && materialStructures.size() > 0) { |
||||||
|
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); |
||||||
|
materials = new Material[materialStructures.size()]; |
||||||
|
int i = 0; |
||||||
|
for (Structure s : materialStructures) { |
||||||
|
Material material = (Material) dataRepository.getLoadedFeature(s.getOldMemoryAddress(), |
||||||
|
LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
if (material == null) { |
||||||
|
material = materialHelper.toMaterial(s, dataRepository); |
||||||
|
} |
||||||
|
materials[i++] = material; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return materials; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method converts rgb values to hsv values. |
||||||
|
* |
||||||
|
* @param rgb |
||||||
|
* rgb values of the color |
||||||
|
* @param hsv |
||||||
|
* hsv values of a color (this table contains the result of the transformation) |
||||||
|
*/ |
||||||
|
public void rgbToHsv(float r, float g, float b, float[] hsv) { |
||||||
|
float cmax = r; |
||||||
|
float cmin = r; |
||||||
|
cmax = g > cmax ? g : cmax; |
||||||
|
cmin = g < cmin ? g : cmin; |
||||||
|
cmax = b > cmax ? b : cmax; |
||||||
|
cmin = b < cmin ? b : cmin; |
||||||
|
|
||||||
|
hsv[2] = cmax; /* value */ |
||||||
|
if (cmax != 0.0) { |
||||||
|
hsv[1] = (cmax - cmin) / cmax; |
||||||
|
} else { |
||||||
|
hsv[1] = 0.0f; |
||||||
|
hsv[0] = 0.0f; |
||||||
|
} |
||||||
|
if (hsv[1] == 0.0) { |
||||||
|
hsv[0] = -1.0f; |
||||||
|
} else { |
||||||
|
float cdelta = cmax - cmin; |
||||||
|
float rc = (cmax - r) / cdelta; |
||||||
|
float gc = (cmax - g) / cdelta; |
||||||
|
float bc = (cmax - b) / cdelta; |
||||||
|
if (r == cmax) { |
||||||
|
hsv[0] = bc - gc; |
||||||
|
} else if (g == cmax) { |
||||||
|
hsv[0] = 2.0f + rc - bc; |
||||||
|
} else { |
||||||
|
hsv[0] = 4.0f + gc - rc; |
||||||
|
} |
||||||
|
hsv[0] *= 60.0f; |
||||||
|
if (hsv[0] < 0.0f) { |
||||||
|
hsv[0] += 360.0f; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
hsv[0] /= 360.0f; |
||||||
|
if (hsv[0] < 0.0f) { |
||||||
|
hsv[0] = 0.0f; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method converts rgb values to hsv values. |
||||||
|
* |
||||||
|
* @param h |
||||||
|
* hue |
||||||
|
* @param s |
||||||
|
* saturation |
||||||
|
* @param v |
||||||
|
* value |
||||||
|
* @param rgb |
||||||
|
* rgb result vector (should have 3 elements) |
||||||
|
*/ |
||||||
|
public void hsvToRgb(float h, float s, float v, float[] rgb) { |
||||||
|
h *= 360.0f; |
||||||
|
if (s == 0.0) { |
||||||
|
rgb[0] = rgb[1] = rgb[2] = v; |
||||||
|
} else { |
||||||
|
if (h == 360) { |
||||||
|
h = 0; |
||||||
|
} else { |
||||||
|
h /= 60; |
||||||
|
} |
||||||
|
int i = (int) Math.floor(h); |
||||||
|
float f = h - i; |
||||||
|
float p = v * (1.0f - s); |
||||||
|
float q = v * (1.0f - s * f); |
||||||
|
float t = v * (1.0f - s * (1.0f - f)); |
||||||
|
switch (i) { |
||||||
|
case 0: |
||||||
|
rgb[0] = v; |
||||||
|
rgb[1] = t; |
||||||
|
rgb[2] = p; |
||||||
|
break; |
||||||
|
case 1: |
||||||
|
rgb[0] = q; |
||||||
|
rgb[1] = v; |
||||||
|
rgb[2] = p; |
||||||
|
break; |
||||||
|
case 2: |
||||||
|
rgb[0] = p; |
||||||
|
rgb[1] = v; |
||||||
|
rgb[2] = t; |
||||||
|
break; |
||||||
|
case 3: |
||||||
|
rgb[0] = p; |
||||||
|
rgb[1] = q; |
||||||
|
rgb[2] = v; |
||||||
|
break; |
||||||
|
case 4: |
||||||
|
rgb[0] = t; |
||||||
|
rgb[1] = p; |
||||||
|
rgb[2] = v; |
||||||
|
break; |
||||||
|
case 5: |
||||||
|
rgb[0] = v; |
||||||
|
rgb[1] = p; |
||||||
|
rgb[2] = q; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,599 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.LinkedList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
|
||||||
|
import com.jme3.asset.BlenderKey.FeaturesToLoad; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Vector2f; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.VertexBuffer; |
||||||
|
import com.jme3.scene.VertexBuffer.Format; |
||||||
|
import com.jme3.scene.VertexBuffer.Type; |
||||||
|
import com.jme3.scene.VertexBuffer.Usage; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
import com.jme3.texture.Texture; |
||||||
|
import com.jme3.util.BufferUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that is used in mesh calculations. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
public class MeshHelper extends AbstractBlenderHelper { |
||||||
|
protected static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // have no idea why 4, could someone please explain ?
|
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender |
||||||
|
* versions. |
||||||
|
* |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public MeshHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data. |
||||||
|
* |
||||||
|
* @param structure |
||||||
|
* the structure we read the mesh from |
||||||
|
* @return the mesh feature |
||||||
|
* @throws BlenderFileException |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public List<Geometry> toMesh(Structure structure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
List<Geometry> geometries = (List<Geometry>) dataRepository.getLoadedFeature(structure.getOldMemoryAddress(), |
||||||
|
LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
if (geometries != null) { |
||||||
|
List<Geometry> copiedGeometries = new ArrayList<Geometry>(geometries.size()); |
||||||
|
for (Geometry geometry : geometries) { |
||||||
|
copiedGeometries.add(geometry.clone()); |
||||||
|
} |
||||||
|
return copiedGeometries; |
||||||
|
} |
||||||
|
|
||||||
|
// helpers
|
||||||
|
TextureHelper textureHelper = dataRepository.getHelper(TextureHelper.class); |
||||||
|
|
||||||
|
// reading mesh data
|
||||||
|
String name = structure.getName(); |
||||||
|
|
||||||
|
// reading vertices
|
||||||
|
Vector3f[] vertices = this.getVertices(structure, dataRepository); |
||||||
|
int verticesAmount = vertices.length; |
||||||
|
|
||||||
|
// vertices Colors
|
||||||
|
List<float[]> verticesColors = this.getVerticesColors(structure, dataRepository); |
||||||
|
|
||||||
|
// reading faces
|
||||||
|
// the following map sorts faces by material number (because in jme Mesh can have only one material)
|
||||||
|
Map<Integer, List<Integer>> meshesMap = new HashMap<Integer, List<Integer>>(); |
||||||
|
Pointer pMFace = (Pointer) structure.getFieldValue("mface"); |
||||||
|
List<Structure> mFaces = pMFace.fetchData(dataRepository.getInputStream()); |
||||||
|
|
||||||
|
Pointer pMTFace = (Pointer) structure.getFieldValue("mtface"); |
||||||
|
List<Vector2f> uvCoordinates = null; |
||||||
|
List<Structure> mtFaces = null; |
||||||
|
|
||||||
|
if (!pMTFace.isNull()) { |
||||||
|
mtFaces = pMTFace.fetchData(dataRepository.getInputStream()); |
||||||
|
int facesAmount = ((Number) structure.getFieldValue("totface")).intValue(); |
||||||
|
if (mtFaces.size() != facesAmount) { |
||||||
|
throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!"); |
||||||
|
} |
||||||
|
uvCoordinates = new ArrayList<Vector2f>();// TODO: calculate the amount of coordinates if possible
|
||||||
|
} |
||||||
|
|
||||||
|
// normalMap merges normals of faces that will be rendered smooth
|
||||||
|
Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(verticesAmount); |
||||||
|
|
||||||
|
List<Vector3f> normalList = new ArrayList<Vector3f>(); |
||||||
|
List<Vector3f> vertexList = new ArrayList<Vector3f>(); |
||||||
|
Map<Integer, Texture> materialNumberToTexture = new HashMap<Integer, Texture>();// indicates if the material with the specified
|
||||||
|
// number should have a texture attached
|
||||||
|
// this map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
|
||||||
|
// positions (it simply tells which vertex is referenced where in the result list)
|
||||||
|
Map<Integer, List<Integer>> vertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAmount); |
||||||
|
int vertexColorIndex = 0; |
||||||
|
for (int i = 0; i < mFaces.size(); ++i) { |
||||||
|
Structure mFace = mFaces.get(i); |
||||||
|
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; |
||||||
|
DynamicArray<Number> uvs = null; |
||||||
|
boolean materialWithoutTextures = false; |
||||||
|
Pointer pImage = null; |
||||||
|
if (mtFaces != null) { |
||||||
|
Structure mtFace = mtFaces.get(i); |
||||||
|
pImage = (Pointer) mtFace.getFieldValue("tpage"); |
||||||
|
materialWithoutTextures = pImage.isNull(); |
||||||
|
// uvs always must be added wheater we have texture or not
|
||||||
|
uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv"); |
||||||
|
uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue())); |
||||||
|
uvCoordinates.add(new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue())); |
||||||
|
uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue())); |
||||||
|
} |
||||||
|
int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue(); |
||||||
|
Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr); |
||||||
|
List<Integer> indexList = meshesMap.get(materialNumber); |
||||||
|
if (indexList == null) { |
||||||
|
indexList = new ArrayList<Integer>(); |
||||||
|
meshesMap.put(materialNumber, indexList); |
||||||
|
} |
||||||
|
if (pImage != null && !pImage.isNull() && !materialNumberToTexture.containsKey(materialNumber)) {// attaching image to texture
|
||||||
|
// (face can have UV's and
|
||||||
|
// image whlie its material
|
||||||
|
// may have no texture
|
||||||
|
// attached)
|
||||||
|
Texture texture = textureHelper.getTextureFromImage(pImage.fetchData(dataRepository.getInputStream()).get(0), |
||||||
|
dataRepository); |
||||||
|
if (texture != null) { |
||||||
|
materialNumberToTexture.put(materialNumber, texture); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); |
||||||
|
int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); |
||||||
|
int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); |
||||||
|
int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); |
||||||
|
|
||||||
|
Vector3f n = FastMath.computeNormal(vertices[v1], vertices[v2], vertices[v3]); |
||||||
|
this.addNormal(n, normalMap, smooth, vertices[v1], vertices[v2], vertices[v3]); |
||||||
|
normalList.add(normalMap.get(vertices[v1])); |
||||||
|
normalList.add(normalMap.get(vertices[v2])); |
||||||
|
normalList.add(normalMap.get(vertices[v3])); |
||||||
|
|
||||||
|
this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap); |
||||||
|
indexList.add(vertexList.size()); |
||||||
|
vertexList.add(vertices[v1]); |
||||||
|
|
||||||
|
this.appendVertexReference(v2, vertexList.size(), vertexReferenceMap); |
||||||
|
indexList.add(vertexList.size()); |
||||||
|
vertexList.add(vertices[v2]); |
||||||
|
|
||||||
|
this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap); |
||||||
|
indexList.add(vertexList.size()); |
||||||
|
vertexList.add(vertices[v3]); |
||||||
|
|
||||||
|
if (v4 > 0) { |
||||||
|
if (uvs != null) { |
||||||
|
uvCoordinates.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue())); |
||||||
|
uvCoordinates.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue())); |
||||||
|
uvCoordinates.add(new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue())); |
||||||
|
} |
||||||
|
this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap); |
||||||
|
indexList.add(vertexList.size()); |
||||||
|
vertexList.add(vertices[v1]); |
||||||
|
|
||||||
|
this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap); |
||||||
|
indexList.add(vertexList.size()); |
||||||
|
vertexList.add(vertices[v3]); |
||||||
|
|
||||||
|
this.appendVertexReference(v4, vertexList.size(), vertexReferenceMap); |
||||||
|
indexList.add(vertexList.size()); |
||||||
|
vertexList.add(vertices[v4]); |
||||||
|
|
||||||
|
this.addNormal(n, normalMap, smooth, vertices[v4]); |
||||||
|
normalList.add(normalMap.get(vertices[v1])); |
||||||
|
normalList.add(normalMap.get(vertices[v3])); |
||||||
|
normalList.add(normalMap.get(vertices[v4])); |
||||||
|
|
||||||
|
if (verticesColors != null) { |
||||||
|
verticesColors.add(vertexColorIndex + 3, verticesColors.get(vertexColorIndex)); |
||||||
|
verticesColors.add(vertexColorIndex + 4, verticesColors.get(vertexColorIndex + 2)); |
||||||
|
} |
||||||
|
vertexColorIndex += 6; |
||||||
|
} else { |
||||||
|
if (verticesColors != null) { |
||||||
|
verticesColors.remove(vertexColorIndex + 3); |
||||||
|
vertexColorIndex += 3; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Vector3f[] normals = normalList.toArray(new Vector3f[normalList.size()]); |
||||||
|
|
||||||
|
// reading vertices groups (from the parent)
|
||||||
|
Structure parent = dataRepository.peekParent(); |
||||||
|
Structure defbase = (Structure) parent.getFieldValue("defbase"); |
||||||
|
List<Structure> defs = defbase.evaluateListBase(dataRepository); |
||||||
|
String[] verticesGroups = new String[defs.size()]; |
||||||
|
int defIndex = 0; |
||||||
|
for (Structure def : defs) { |
||||||
|
verticesGroups[defIndex++] = def.getFieldValue("name").toString(); |
||||||
|
} |
||||||
|
|
||||||
|
// vertices bone weights and indices
|
||||||
|
ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class); |
||||||
|
Structure defBase = (Structure) parent.getFieldValue("defbase"); |
||||||
|
Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, dataRepository); |
||||||
|
|
||||||
|
VertexBuffer verticesWeights = null, verticesWeightsIndices = null; |
||||||
|
int[] bonesGroups = new int[] { 0 }; |
||||||
|
VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(structure, vertexList.size(), bonesGroups, |
||||||
|
vertexReferenceMap, groupToBoneIndexMap, dataRepository); |
||||||
|
verticesWeights = boneWeightsAndIndex[0]; |
||||||
|
verticesWeightsIndices = boneWeightsAndIndex[1]; |
||||||
|
|
||||||
|
// reading materials
|
||||||
|
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); |
||||||
|
Material[] materials = null; |
||||||
|
Material[] nonTexturedMaterials = null; |
||||||
|
if ((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { |
||||||
|
materials = materialHelper.getMaterials(structure, dataRepository); |
||||||
|
nonTexturedMaterials = materials == null ? null : new Material[materials.length];// fill it when needed
|
||||||
|
} |
||||||
|
|
||||||
|
// creating the result meshes
|
||||||
|
geometries = new ArrayList<Geometry>(meshesMap.size()); |
||||||
|
|
||||||
|
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); |
||||||
|
verticesBuffer.setupData(Usage.Stream, 3, Format.Float, |
||||||
|
BufferUtils.createFloatBuffer(vertexList.toArray(new Vector3f[vertexList.size()]))); |
||||||
|
|
||||||
|
// initial vertex position (used with animation)
|
||||||
|
VertexBuffer verticesBind = new VertexBuffer(Type.BindPosePosition); |
||||||
|
verticesBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(verticesBuffer.getData())); |
||||||
|
|
||||||
|
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); |
||||||
|
normalsBuffer.setupData(Usage.Stream, 3, Format.Float, BufferUtils.createFloatBuffer(normals)); |
||||||
|
|
||||||
|
// initial normals position (used with animation)
|
||||||
|
VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal); |
||||||
|
normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData())); |
||||||
|
|
||||||
|
VertexBuffer uvCoordsBuffer = null; |
||||||
|
if (uvCoordinates != null) { |
||||||
|
uvCoordsBuffer = new VertexBuffer(Type.TexCoord); |
||||||
|
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, |
||||||
|
BufferUtils.createFloatBuffer(uvCoordinates.toArray(new Vector2f[uvCoordinates.size()]))); |
||||||
|
} |
||||||
|
|
||||||
|
// generating meshes
|
||||||
|
FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors); |
||||||
|
for (Entry<Integer, List<Integer>> meshEntry : meshesMap.entrySet()) { |
||||||
|
Mesh mesh = new Mesh(); |
||||||
|
|
||||||
|
// creating vertices indices for this mesh
|
||||||
|
List<Integer> indexList = meshEntry.getValue(); |
||||||
|
int[] indices = new int[indexList.size()]; |
||||||
|
for (int i = 0; i < indexList.size(); ++i) { |
||||||
|
indices[i] = indexList.get(i).intValue(); |
||||||
|
} |
||||||
|
|
||||||
|
// setting vertices
|
||||||
|
mesh.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indices)); |
||||||
|
mesh.setBuffer(verticesBuffer); |
||||||
|
mesh.setBuffer(verticesBind); |
||||||
|
|
||||||
|
// setting vertices colors
|
||||||
|
if (verticesColorsBuffer != null) { |
||||||
|
mesh.setBuffer(Type.Color, 4, verticesColorsBuffer); |
||||||
|
} |
||||||
|
|
||||||
|
// setting weights for bones
|
||||||
|
if (verticesWeights != null) { |
||||||
|
mesh.setMaxNumWeights(bonesGroups[0]); |
||||||
|
mesh.setBuffer(verticesWeights); |
||||||
|
mesh.setBuffer(verticesWeightsIndices); |
||||||
|
} |
||||||
|
|
||||||
|
// setting faces' normals
|
||||||
|
mesh.setBuffer(normalsBuffer); |
||||||
|
mesh.setBuffer(normalsBind); |
||||||
|
|
||||||
|
// setting uvCoords
|
||||||
|
if (uvCoordsBuffer != null) { |
||||||
|
mesh.setBuffer(uvCoordsBuffer); |
||||||
|
} |
||||||
|
|
||||||
|
// creating the result
|
||||||
|
Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh); |
||||||
|
if (materials != null) { |
||||||
|
int materialNumber = meshEntry.getKey().intValue(); |
||||||
|
Material material; |
||||||
|
if (materialNumber >= 0) { |
||||||
|
material = materials[materialNumber]; |
||||||
|
if (materialNumberToTexture.containsKey(Integer.valueOf(materialNumber))) { |
||||||
|
if (material.getMaterialDef().getAssetName().contains("Lighting")) { |
||||||
|
if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_DIFFUSE)) { |
||||||
|
material = material.clone(); |
||||||
|
material.setTexture(MaterialHelper.TEXTURE_TYPE_DIFFUSE, |
||||||
|
materialNumberToTexture.get(Integer.valueOf(materialNumber))); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (!materialHelper.hasTexture(material, MaterialHelper.TEXTURE_TYPE_COLOR)) { |
||||||
|
material = material.clone(); |
||||||
|
material.setTexture(MaterialHelper.TEXTURE_TYPE_COLOR, |
||||||
|
materialNumberToTexture.get(Integer.valueOf(materialNumber))); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
materialNumber = -1 * (materialNumber + 1); |
||||||
|
if (nonTexturedMaterials[materialNumber] == null) { |
||||||
|
nonTexturedMaterials[materialNumber] = materialHelper.getNonTexturedMaterial(materials[materialNumber], |
||||||
|
TextureHelper.TEX_IMAGE); |
||||||
|
} |
||||||
|
material = nonTexturedMaterials[materialNumber]; |
||||||
|
} |
||||||
|
geometry.setMaterial(material); |
||||||
|
} else { |
||||||
|
geometry.setMaterial(dataRepository.getDefaultMaterial()); |
||||||
|
} |
||||||
|
geometries.add(geometry); |
||||||
|
} |
||||||
|
dataRepository.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); |
||||||
|
return geometries; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth. |
||||||
|
* |
||||||
|
* @param normalToAdd |
||||||
|
* a normal to be added |
||||||
|
* @param normalMap |
||||||
|
* merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector |
||||||
|
* @param smooth |
||||||
|
* the variable that indicates wheather to merge normals (creating the smooth mesh) or not |
||||||
|
* @param vertices |
||||||
|
* a list of vertices read from the blender file |
||||||
|
*/ |
||||||
|
protected void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) { |
||||||
|
for (Vector3f v : vertices) { |
||||||
|
Vector3f n = normalMap.get(v); |
||||||
|
if (!smooth || n == null) { |
||||||
|
normalMap.put(v, normalToAdd.clone()); |
||||||
|
} else { |
||||||
|
n.addLocal(normalToAdd).normalizeLocal(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created |
||||||
|
* to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key |
||||||
|
* - the reference indices list. |
||||||
|
* |
||||||
|
* @param basicVertexIndex |
||||||
|
* the index of the vertex from its basic table |
||||||
|
* @param resultIndex |
||||||
|
* the index of the vertex in its result vertex list |
||||||
|
* @param vertexReferenceMap |
||||||
|
* the reference map |
||||||
|
*/ |
||||||
|
protected void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) { |
||||||
|
List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); |
||||||
|
if (referenceList == null) { |
||||||
|
referenceList = new ArrayList<Integer>(); |
||||||
|
vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); |
||||||
|
} |
||||||
|
referenceList.add(Integer.valueOf(resultIndex)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the vertices colors. Each vertex is stored in float[4] array. |
||||||
|
* |
||||||
|
* @param meshStructure |
||||||
|
* the structure containing the mesh data |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return a list of vertices colors, each color belongs to a single vertex |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blend file structure is somehow invalid or corrupted |
||||||
|
*/ |
||||||
|
public List<float[]> getVerticesColors(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
Pointer pMCol = (Pointer) meshStructure.getFieldValue("mcol"); |
||||||
|
List<float[]> verticesColors = null; |
||||||
|
List<Structure> mCol = null; |
||||||
|
if (!pMCol.isNull()) { |
||||||
|
verticesColors = new LinkedList<float[]>(); |
||||||
|
mCol = pMCol.fetchData(dataRepository.getInputStream()); |
||||||
|
for (Structure color : mCol) { |
||||||
|
float r = ((Number) color.getFieldValue("r")).byteValue() / 256.0f; |
||||||
|
float g = ((Number) color.getFieldValue("g")).byteValue() / 256.0f; |
||||||
|
float b = ((Number) color.getFieldValue("b")).byteValue() / 256.0f; |
||||||
|
float a = ((Number) color.getFieldValue("a")).byteValue() / 256.0f; |
||||||
|
verticesColors.add(new float[] { b, g, r, a }); |
||||||
|
} |
||||||
|
} |
||||||
|
return verticesColors; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the vertices. |
||||||
|
* |
||||||
|
* @param meshStructure |
||||||
|
* the structure containing the mesh data |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return a list of vertices colors, each color belongs to a single vertex |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blend file structure is somehow invalid or corrupted |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public Vector3f[] getVertices(Structure meshStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
int verticesAmount = ((Number) meshStructure.getFieldValue("totvert")).intValue(); |
||||||
|
Vector3f[] vertices = new Vector3f[verticesAmount]; |
||||||
|
Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert"); |
||||||
|
List<Structure> mVerts = pMVert.fetchData(dataRepository.getInputStream()); |
||||||
|
for (int i = 0; i < verticesAmount; ++i) { |
||||||
|
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co"); |
||||||
|
vertices[i] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); |
||||||
|
} |
||||||
|
return vertices; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns an array of size 2. The first element is a vertex buffer holding bone weights for every vertex in the model. The |
||||||
|
* second element is a vertex buffer holding bone indices for vertices (the indices of bones the vertices are assigned to). |
||||||
|
* |
||||||
|
* @param meshStructure |
||||||
|
* the mesh structure object |
||||||
|
* @param vertexListSize |
||||||
|
* a number of vertices in the model |
||||||
|
* @param bonesGroups |
||||||
|
* this is an output parameter, it should be a one-sized array; the maximum amount of weights per vertex (up to |
||||||
|
* MAXIMUM_WEIGHTS_PER_VERTEX) is stored there |
||||||
|
* @param vertexReferenceMap |
||||||
|
* this reference map allows to map the original vertices read from blender to vertices that are really in the model; one |
||||||
|
* vertex may appear several times in the result model |
||||||
|
* @param groupToBoneIndexMap |
||||||
|
* this object maps the group index (to which a vertices in blender belong) to bone index of the model |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return arrays of vertices weights and their bone indices and (as an outpot parameter) the maximum amount of weights for a vertex |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blend file structure is somehow invalid or corrupted |
||||||
|
*/ |
||||||
|
public VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, |
||||||
|
Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, DataRepository dataRepository) |
||||||
|
throws BlenderFileException { |
||||||
|
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
|
||||||
|
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); |
||||||
|
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); |
||||||
|
if (!pDvert.isNull()) {// assigning weights and bone indices
|
||||||
|
List<Structure> dverts = pDvert.fetchData(dataRepository.getInputStream());// dverts.size() == verticesAmount (one dvert per
|
||||||
|
// vertex in blender)
|
||||||
|
int vertexIndex = 0; |
||||||
|
for (Structure dvert : dverts) { |
||||||
|
int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex
|
||||||
|
// (max. 4 in JME)
|
||||||
|
Pointer pDW = (Pointer) dvert.getFieldValue("dw"); |
||||||
|
List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here
|
||||||
|
if (totweight > 0 && !pDW.isNull()) {// pDW should never be null here, but I check it just in case :)
|
||||||
|
int weightIndex = 0; |
||||||
|
List<Structure> dw = pDW.fetchData(dataRepository.getInputStream()); |
||||||
|
for (Structure deformWeight : dw) { |
||||||
|
Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); |
||||||
|
if (boneIndex != null) {// null here means that we came accross group that has no bone attached to
|
||||||
|
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); |
||||||
|
if (weight == 0.0f) { |
||||||
|
weight = 1; |
||||||
|
boneIndex = Integer.valueOf(0); |
||||||
|
} |
||||||
|
// we apply the weight to all referenced vertices
|
||||||
|
for (Integer index : vertexIndices) { |
||||||
|
// all indices are always assigned to 0-indexed bone
|
||||||
|
// weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, 1.0f);
|
||||||
|
// indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, (byte)0);
|
||||||
|
// if(weight != 0.0f) {
|
||||||
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); |
||||||
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); |
||||||
|
// }
|
||||||
|
} |
||||||
|
} |
||||||
|
++weightIndex; |
||||||
|
} |
||||||
|
} else { |
||||||
|
for (Integer index : vertexIndices) { |
||||||
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); |
||||||
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); |
||||||
|
} |
||||||
|
} |
||||||
|
++vertexIndex; |
||||||
|
} |
||||||
|
} else { |
||||||
|
// always bind all vertices to 0-indexed bone
|
||||||
|
// this bone makes the model look normally if vertices have no bone assigned
|
||||||
|
// and it is used in object animation, so if we come accross object animation
|
||||||
|
// we can use the 0-indexed bone for this
|
||||||
|
for (List<Integer> vertexIndexList : vertexReferenceMap.values()) { |
||||||
|
// we apply the weight to all referenced vertices
|
||||||
|
for (Integer index : vertexIndexList) { |
||||||
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); |
||||||
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData); |
||||||
|
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); |
||||||
|
verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData); |
||||||
|
|
||||||
|
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); |
||||||
|
verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData); |
||||||
|
return new VertexBuffer[] { verticesWeights, verticesWeightsIndices }; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Normalizes weights if needed and finds largest amount of weights used for all vertices in the buffer. |
||||||
|
*/ |
||||||
|
protected int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) { |
||||||
|
int maxWeightsPerVert = 0; |
||||||
|
weightsFloatData.rewind(); |
||||||
|
for (int v = 0; v < vertCount; ++v) { |
||||||
|
float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get(); |
||||||
|
|
||||||
|
if (w3 != 0) { |
||||||
|
maxWeightsPerVert = Math.max(maxWeightsPerVert, 4); |
||||||
|
} else if (w2 != 0) { |
||||||
|
maxWeightsPerVert = Math.max(maxWeightsPerVert, 3); |
||||||
|
} else if (w1 != 0) { |
||||||
|
maxWeightsPerVert = Math.max(maxWeightsPerVert, 2); |
||||||
|
} else if (w0 != 0) { |
||||||
|
maxWeightsPerVert = Math.max(maxWeightsPerVert, 1); |
||||||
|
} |
||||||
|
|
||||||
|
float sum = w0 + w1 + w2 + w3; |
||||||
|
if (sum != 1f && sum != 0.0f) { |
||||||
|
weightsFloatData.position(weightsFloatData.position() - 4); |
||||||
|
// compute new vals based on sum
|
||||||
|
float sumToB = 1f / sum; |
||||||
|
weightsFloatData.put(w0 * sumToB); |
||||||
|
weightsFloatData.put(w1 * sumToB); |
||||||
|
weightsFloatData.put(w2 * sumToB); |
||||||
|
weightsFloatData.put(w3 * sumToB); |
||||||
|
} |
||||||
|
} |
||||||
|
weightsFloatData.rewind(); |
||||||
|
|
||||||
|
// mesh.setMaxNumWeights(maxWeightsPerVert);
|
||||||
|
return maxWeightsPerVert; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,527 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.animation.AnimControl; |
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.BoneAnimation; |
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.animation.SkeletonControl; |
||||||
|
import com.jme3.bounding.BoundingBox; |
||||||
|
import com.jme3.bounding.BoundingSphere; |
||||||
|
import com.jme3.bounding.BoundingVolume; |
||||||
|
import com.jme3.effect.EmitterMeshVertexShape; |
||||||
|
import com.jme3.effect.EmitterShape; |
||||||
|
import com.jme3.effect.ParticleEmitter; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.plugins.blender.data.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ParticlesHelper; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Constraint; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Modifier; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
import com.jme3.scene.plugins.ogre.AnimData; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that is used in modifiers calculations. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class ModifierHelper extends AbstractBlenderHelper { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName()); |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in |
||||||
|
* different blender versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public ModifierHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method applies modifier to the object. |
||||||
|
* @param node |
||||||
|
* the loaded object |
||||||
|
* @param modifier |
||||||
|
* the modifier to apply |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return the node to whom the modifier was applied |
||||||
|
*/ |
||||||
|
public Node applyModifier(Node node, Modifier modifier, DataRepository dataRepository) { |
||||||
|
if(Modifier.ARMATURE_MODIFIER_DATA.equals(modifier.getType())) { |
||||||
|
return this.applyArmatureModifierData(node, modifier, dataRepository); |
||||||
|
} else if(Modifier.ARRAY_MODIFIER_DATA.equals(modifier.getType())) { |
||||||
|
return this.applyArrayModifierData(node, modifier, dataRepository); |
||||||
|
} else if(Modifier.PARTICLE_MODIFIER_DATA.equals(modifier.getType())) { |
||||||
|
return this.applyParticleSystemModifierData(node, modifier, dataRepository); |
||||||
|
} else { |
||||||
|
LOGGER.warning("Modifier: " + modifier.getType() + " not yet implemented!!!"); |
||||||
|
return node; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads the given object's modifiers. |
||||||
|
* @param objectStructure |
||||||
|
* the object structure |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @param converter |
||||||
|
* the converter object (in some cases we need to read an object first before loading the modifier) |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow corrupted |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void readModifiers(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
Structure modifiersListBase = (Structure)objectStructure.getFieldValue("modifiers"); |
||||||
|
List<Structure> modifiers = modifiersListBase.evaluateListBase(dataRepository); |
||||||
|
for(Structure modifier : modifiers) { |
||||||
|
Object loadedModifier = null; |
||||||
|
Object modifierAdditionalData = null; |
||||||
|
if(Modifier.ARRAY_MODIFIER_DATA.equals(modifier.getType())) {//****************ARRAY MODIFIER
|
||||||
|
Map<String, Object> params = new HashMap<String, Object>(); |
||||||
|
|
||||||
|
Number fittype = (Number) modifier.getFieldValue("fit_type"); |
||||||
|
params.put("fittype", fittype); |
||||||
|
switch(fittype.intValue()) { |
||||||
|
case 0://FIXED COUNT
|
||||||
|
params.put("count", modifier.getFieldValue("count")); |
||||||
|
break; |
||||||
|
case 1://FIXED LENGTH
|
||||||
|
params.put("length", modifier.getFieldValue("length")); |
||||||
|
break; |
||||||
|
case 2://FITCURVE
|
||||||
|
//TODO: implement after loading curves is added; warning will be generated during modifier applying
|
||||||
|
break; |
||||||
|
default: |
||||||
|
assert false : "Unknown array modifier fit type: " + fittype; |
||||||
|
} |
||||||
|
|
||||||
|
//offset parameters
|
||||||
|
int offsettype = ((Number) modifier.getFieldValue("offset_type")).intValue(); |
||||||
|
if((offsettype & 0x01) != 0) {//Constant offset
|
||||||
|
DynamicArray<Number> offsetArray = (DynamicArray<Number>)modifier.getFieldValue("offset"); |
||||||
|
float[] offset = new float[] {offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue()}; |
||||||
|
params.put("offset", offset); |
||||||
|
} |
||||||
|
if((offsettype & 0x02) != 0) {//Relative offset
|
||||||
|
DynamicArray<Number> scaleArray = (DynamicArray<Number>)modifier.getFieldValue("scale"); |
||||||
|
float[] scale = new float[] {scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()}; |
||||||
|
params.put("scale", scale); |
||||||
|
} |
||||||
|
if((offsettype & 0x04) != 0) {//Object offset
|
||||||
|
Pointer pOffsetObject = (Pointer)modifier.getFieldValue("offset_ob"); |
||||||
|
if(!pOffsetObject.isNull()) { |
||||||
|
params.put("offsetob", pOffsetObject); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//start cap and end cap
|
||||||
|
Pointer pStartCap = (Pointer)modifier.getFieldValue("start_cap"); |
||||||
|
if(!pStartCap.isNull()) { |
||||||
|
params.put("startcap", pStartCap); |
||||||
|
} |
||||||
|
Pointer pEndCap = (Pointer)modifier.getFieldValue("end_cap"); |
||||||
|
if(!pEndCap.isNull()) { |
||||||
|
params.put("endcap", pEndCap); |
||||||
|
} |
||||||
|
loadedModifier = params; |
||||||
|
} else if(Modifier.ARMATURE_MODIFIER_DATA.equals(modifier.getType())) {//****************ARMATURE MODIFIER
|
||||||
|
Pointer pArmatureObject = (Pointer)modifier.getFieldValue("object"); |
||||||
|
if(!pArmatureObject.isNull()) { |
||||||
|
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); |
||||||
|
Structure armatureObject = (Structure)dataRepository.getLoadedFeature(pArmatureObject.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_STRUCTURE); |
||||||
|
if(armatureObject == null) {//we check this first not to fetch the structure unnecessary
|
||||||
|
armatureObject = pArmatureObject.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
objectHelper.toObject(armatureObject, dataRepository); |
||||||
|
} |
||||||
|
modifierAdditionalData = armatureObject.getOldMemoryAddress(); |
||||||
|
ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class); |
||||||
|
|
||||||
|
//changing bones matrices so that they fit the current object (taht is why we need a copy of a skeleton)
|
||||||
|
Matrix4f armatureObjectMatrix = objectHelper.getTransformationMatrix(armatureObject); |
||||||
|
Matrix4f inverseMeshObjectMatrix = objectHelper.getTransformationMatrix(objectStructure).invert(); |
||||||
|
Matrix4f additionalRootBoneTransformation = inverseMeshObjectMatrix.multLocal(armatureObjectMatrix); |
||||||
|
Bone[] bones = armatureHelper.buildBonesStructure(Long.valueOf(0L), additionalRootBoneTransformation); |
||||||
|
|
||||||
|
String objectName = objectStructure.getName(); |
||||||
|
Set<String> animationNames = dataRepository.getBlenderKey().getAnimationNames(objectName); |
||||||
|
if(animationNames != null && animationNames.size() > 0) { |
||||||
|
ArrayList<BoneAnimation> animations = new ArrayList<BoneAnimation>(); |
||||||
|
List<FileBlockHeader> actionHeaders = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); |
||||||
|
for(FileBlockHeader header : actionHeaders) { |
||||||
|
Structure actionStructure = header.getStructure(dataRepository); |
||||||
|
String actionName = actionStructure.getName(); |
||||||
|
if(animationNames.contains(actionName)) { |
||||||
|
int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, actionName); |
||||||
|
int fps = dataRepository.getBlenderKey().getFps(); |
||||||
|
float start = (float)animationFrames[0] / (float)fps; |
||||||
|
float stop = (float)animationFrames[1] / (float)fps; |
||||||
|
BoneAnimation boneAnimation = new BoneAnimation(actionName, stop - start); |
||||||
|
boneAnimation.setTracks(armatureHelper.getTracks(actionStructure, dataRepository, objectName, actionName)); |
||||||
|
animations.add(boneAnimation); |
||||||
|
} |
||||||
|
} |
||||||
|
loadedModifier = new AnimData(new Skeleton(bones), animations); |
||||||
|
} |
||||||
|
} else { |
||||||
|
LOGGER.warning("Unsupported modifier type: " + modifier.getType()); |
||||||
|
} |
||||||
|
} else if(Modifier.PARTICLE_MODIFIER_DATA.equals(modifier.getType())) {//****************PARTICLES MODIFIER
|
||||||
|
Pointer pParticleSystem = (Pointer) modifier.getFieldValue("psys"); |
||||||
|
if(!pParticleSystem.isNull()) { |
||||||
|
ParticlesHelper particlesHelper = dataRepository.getHelper(ParticlesHelper.class); |
||||||
|
Structure particleSystem = pParticleSystem.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
loadedModifier = particlesHelper.toParticleEmitter(particleSystem, dataRepository); |
||||||
|
} |
||||||
|
} |
||||||
|
//adding modifier to the modifier's lists
|
||||||
|
if(loadedModifier != null) { |
||||||
|
dataRepository.addModifier(objectStructure.getOldMemoryAddress(), modifier.getType(), loadedModifier, modifierAdditionalData); |
||||||
|
modifierAdditionalData = null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected Node applyParticleSystemModifierData(Node node, Modifier modifier, DataRepository dataRepository) { |
||||||
|
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); |
||||||
|
ParticleEmitter emitter = (ParticleEmitter) modifier.getJmeModifierRepresentation(); |
||||||
|
emitter = emitter.clone(); |
||||||
|
|
||||||
|
//applying emitter shape
|
||||||
|
EmitterShape emitterShape = emitter.getShape(); |
||||||
|
if(emitterShape instanceof EmitterMeshVertexShape) { |
||||||
|
List<Mesh> meshes = new ArrayList<Mesh>(); |
||||||
|
for(Spatial spatial : node.getChildren()) { |
||||||
|
if(spatial instanceof Geometry) { |
||||||
|
Mesh mesh = ((Geometry) spatial).getMesh(); |
||||||
|
if(mesh != null) { |
||||||
|
meshes.add(mesh); |
||||||
|
Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), dataRepository); |
||||||
|
emitter.setMaterial(material);//TODO: rozbić na kilka części
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if(meshes.size()>0) { |
||||||
|
((EmitterMeshVertexShape) emitterShape).setMeshes(meshes); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
node.attachChild(emitter); |
||||||
|
return node; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method applies ArmatureModifierData to the loaded object. |
||||||
|
* @param node |
||||||
|
* the loaded object |
||||||
|
* @param modifier |
||||||
|
* the modifier to apply |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return the node to whom the modifier was applied |
||||||
|
*/ |
||||||
|
protected Node applyArmatureModifierData(Node node, Modifier modifier, DataRepository dataRepository) { |
||||||
|
AnimData ad = (AnimData)modifier.getJmeModifierRepresentation(); |
||||||
|
ArrayList<BoneAnimation> animList = ad.anims; |
||||||
|
Long modifierArmatureObject = (Long)modifier.getAdditionalData(); |
||||||
|
if(animList != null && animList.size() > 0) { |
||||||
|
ConstraintHelper constraintHelper = dataRepository.getHelper(ConstraintHelper.class); |
||||||
|
Constraint[] constraints = constraintHelper.getConstraints(modifierArmatureObject); |
||||||
|
HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>(); |
||||||
|
for(int i = 0; i < animList.size(); ++i) { |
||||||
|
BoneAnimation boneAnimation = this.cloneBoneAnimation(animList.get(i)); |
||||||
|
|
||||||
|
//baking constraints into animations
|
||||||
|
if(constraints != null && constraints.length > 0) { |
||||||
|
for(Constraint constraint : constraints) { |
||||||
|
constraint.affectAnimation(ad.skeleton, boneAnimation); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
anims.put(boneAnimation.getName(), boneAnimation); |
||||||
|
} |
||||||
|
|
||||||
|
//getting meshes
|
||||||
|
Mesh[] meshes = null; |
||||||
|
List<Mesh> meshesList = new ArrayList<Mesh>(); |
||||||
|
List<Spatial> children = node.getChildren(); |
||||||
|
for(Spatial child : children) { |
||||||
|
if(child instanceof Geometry) { |
||||||
|
meshesList.add(((Geometry)child).getMesh()); |
||||||
|
} |
||||||
|
} |
||||||
|
if(meshesList.size() > 0) { |
||||||
|
meshes = meshesList.toArray(new Mesh[meshesList.size()]); |
||||||
|
} |
||||||
|
|
||||||
|
//applying the control to the node
|
||||||
|
SkeletonControl skeletonControl = new SkeletonControl(meshes, ad.skeleton); |
||||||
|
AnimControl control = node.getControl(AnimControl.class); |
||||||
|
|
||||||
|
if(control == null) { |
||||||
|
control = new AnimControl(ad.skeleton); |
||||||
|
} else { |
||||||
|
//merging skeletons
|
||||||
|
Skeleton controlSkeleton = control.getSkeleton(); |
||||||
|
int boneIndexIncrease = controlSkeleton.getBoneCount(); |
||||||
|
Skeleton skeleton = this.merge(controlSkeleton, ad.skeleton); |
||||||
|
|
||||||
|
//merging animations
|
||||||
|
HashMap<String, BoneAnimation> animations = new HashMap<String, BoneAnimation>(); |
||||||
|
for(String animationName : control.getAnimationNames()) { |
||||||
|
animations.put(animationName, control.getAnim(animationName)); |
||||||
|
} |
||||||
|
for(Entry<String, BoneAnimation> animEntry : anims.entrySet()) { |
||||||
|
BoneAnimation ba = animEntry.getValue(); |
||||||
|
for(int i = 0; i < ba.getTracks().length; ++i) { |
||||||
|
BoneTrack bt = ba.getTracks()[i]; |
||||||
|
int newBoneIndex = bt.getTargetBoneIndex() + boneIndexIncrease; |
||||||
|
ba.getTracks()[i] = new BoneTrack(newBoneIndex, bt.getTimes(), bt.getTranslations(), bt.getRotations(), bt.getScales()); |
||||||
|
} |
||||||
|
animations.put(animEntry.getKey(), animEntry.getValue()); |
||||||
|
} |
||||||
|
|
||||||
|
//replacing the control
|
||||||
|
node.removeControl(control); |
||||||
|
control = new AnimControl(skeleton); |
||||||
|
} |
||||||
|
control.setAnimations(anims); |
||||||
|
node.addControl(control); |
||||||
|
node.addControl(skeletonControl); |
||||||
|
} |
||||||
|
return node; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method applies the array modifier to the node. |
||||||
|
* @param node |
||||||
|
* the object the modifier will be applied to |
||||||
|
* @param modifier |
||||||
|
* the modifier to be applied |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return object node with arry modifier applied |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
protected Node applyArrayModifierData(Node node, Modifier modifier, DataRepository dataRepository) { |
||||||
|
Map<String, Object> modifierData = (Map<String, Object>) modifier.getJmeModifierRepresentation(); |
||||||
|
int fittype = ((Number)modifierData.get("fittype")).intValue(); |
||||||
|
float[] offset = (float[])modifierData.get("offset"); |
||||||
|
if(offset==null) {//the node will be repeated several times in the same place
|
||||||
|
offset = new float[] {0.0f, 0.0f, 0.0f}; |
||||||
|
} |
||||||
|
float[] scale = (float[])modifierData.get("scale"); |
||||||
|
if(scale==null) {//the node will be repeated several times in the same place
|
||||||
|
scale = new float[] {0.0f, 0.0f, 0.0f}; |
||||||
|
} else { |
||||||
|
//getting bounding box
|
||||||
|
node.updateModelBound(); |
||||||
|
BoundingVolume boundingVolume = node.getWorldBound(); |
||||||
|
if(boundingVolume instanceof BoundingBox) { |
||||||
|
scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f; |
||||||
|
scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f; |
||||||
|
scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f; |
||||||
|
} else if(boundingVolume instanceof BoundingSphere) { |
||||||
|
float radius = ((BoundingSphere) boundingVolume).getRadius(); |
||||||
|
scale[0] *= radius * 2.0f; |
||||||
|
scale[1] *= radius * 2.0f; |
||||||
|
scale[2] *= radius * 2.0f; |
||||||
|
} else { |
||||||
|
throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//adding object's offset
|
||||||
|
float[] objectOffset = new float[] {0.0f, 0.0f, 0.0f}; |
||||||
|
Pointer pOffsetObject = (Pointer) modifierData.get("offsetob"); |
||||||
|
if(pOffsetObject!=null) { |
||||||
|
FileBlockHeader offsetObjectBlock = dataRepository.getFileBlock(pOffsetObject.getOldMemoryAddress()); |
||||||
|
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); |
||||||
|
try {//we take the structure in case the object was not yet loaded
|
||||||
|
Structure offsetStructure = offsetObjectBlock.getStructure(dataRepository); |
||||||
|
Vector3f translation = objectHelper.getTransformation(offsetStructure).getTranslation(); |
||||||
|
objectOffset[0] = translation.x; |
||||||
|
objectOffset[1] = translation.y; |
||||||
|
objectOffset[2] = translation.z; |
||||||
|
} catch (BlenderFileException e) { |
||||||
|
LOGGER.warning("Problems in blender file structure! Object offset cannot be applied! The problem: " + e.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//getting start and end caps
|
||||||
|
Node[] caps = new Node[] {null, null}; |
||||||
|
Pointer[] pCaps = new Pointer[] {(Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap")}; |
||||||
|
for(int i=0;i<pCaps.length;++i) { |
||||||
|
if(pCaps[i]!=null) { |
||||||
|
caps[i] = (Node) dataRepository.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
if(caps[i]!=null) { |
||||||
|
caps[i] = (Node) caps[i].clone(); |
||||||
|
} else { |
||||||
|
FileBlockHeader capBlock = dataRepository.getFileBlock(pOffsetObject.getOldMemoryAddress()); |
||||||
|
try {//we take the structure in case the object was not yet loaded
|
||||||
|
Structure capStructure = capBlock.getStructure(dataRepository); |
||||||
|
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); |
||||||
|
caps[i] = (Node) objectHelper.toObject(capStructure, dataRepository); |
||||||
|
if(caps[i]==null) { |
||||||
|
LOGGER.warning("Cap object '" + capStructure.getName() + "' couldn't be loaded!"); |
||||||
|
} |
||||||
|
} catch (BlenderFileException e) { |
||||||
|
LOGGER.warning("Problems in blender file structure! Cap object cannot be applied! The problem: " + e.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], |
||||||
|
offset[1] + scale[1] + objectOffset[1], |
||||||
|
offset[2] + scale[2] + objectOffset[2]); |
||||||
|
|
||||||
|
//getting/calculating repeats amount
|
||||||
|
int count = 0; |
||||||
|
if(fittype==0) {//Fixed count
|
||||||
|
count = ((Number)modifierData.get("count")).intValue() - 1; |
||||||
|
} else if(fittype==1) {//Fixed length
|
||||||
|
float length = ((Number)modifierData.get("length")).floatValue(); |
||||||
|
if(translationVector.length()>0.0f) { |
||||||
|
count = (int)(length / translationVector.length()) - 1; |
||||||
|
} |
||||||
|
} else if(fittype==2) {//Fit curve
|
||||||
|
LOGGER.warning("Fit curve mode in array modifier not yet implemented!");//TODO: implement fit curve
|
||||||
|
} else { |
||||||
|
throw new IllegalStateException("Unknown fit type: " + fittype); |
||||||
|
} |
||||||
|
|
||||||
|
//adding translated nodes and caps
|
||||||
|
if(count>0) { |
||||||
|
Node[] arrayNodes = new Node[count]; |
||||||
|
Vector3f newTranslation = node.getLocalTranslation().clone(); |
||||||
|
for(int i=0;i<count;++i) { |
||||||
|
newTranslation.addLocal(translationVector); |
||||||
|
Node nodeClone = (Node) node.clone(); |
||||||
|
nodeClone.setLocalTranslation(newTranslation); |
||||||
|
arrayNodes[i] = nodeClone; |
||||||
|
} |
||||||
|
for(Node nodeClone : arrayNodes) { |
||||||
|
node.attachChild(nodeClone); |
||||||
|
} |
||||||
|
if(caps[0]!=null) { |
||||||
|
caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector); |
||||||
|
node.attachChild(caps[0]); |
||||||
|
} |
||||||
|
if(caps[1]!=null) { |
||||||
|
caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector); |
||||||
|
node.attachChild(caps[1]); |
||||||
|
} |
||||||
|
} |
||||||
|
return node; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This class clones the bone animation data. |
||||||
|
* @param source |
||||||
|
* the source that is to be cloned |
||||||
|
* @return the copy of the given bone animation |
||||||
|
*/ |
||||||
|
protected BoneAnimation cloneBoneAnimation(BoneAnimation source) { |
||||||
|
BoneAnimation result = new BoneAnimation(source.getName(), source.getLength()); |
||||||
|
|
||||||
|
//copying tracks and applying constraints
|
||||||
|
BoneTrack[] sourceTracks = source.getTracks(); |
||||||
|
BoneTrack[] boneTracks = new BoneTrack[sourceTracks.length]; |
||||||
|
for(int i = 0; i < sourceTracks.length; ++i) { |
||||||
|
int tablesLength = sourceTracks[i].getTimes().length; |
||||||
|
|
||||||
|
Vector3f[] sourceTranslations = sourceTracks[i].getTranslations(); |
||||||
|
Quaternion[] sourceRotations = sourceTracks[i].getRotations(); |
||||||
|
Vector3f[] sourceScales = sourceTracks[i].getScales(); |
||||||
|
|
||||||
|
Vector3f[] translations = new Vector3f[tablesLength]; |
||||||
|
Quaternion[] rotations = new Quaternion[tablesLength]; |
||||||
|
Vector3f[] scales = sourceScales == null ? null : new Vector3f[tablesLength]; |
||||||
|
for(int j = 0; j < tablesLength; ++j) { |
||||||
|
translations[j] = sourceTranslations[j].clone(); |
||||||
|
rotations[j] = sourceRotations[j].clone(); |
||||||
|
if(sourceScales != null) {//only scales may not be applied
|
||||||
|
scales[j] = sourceScales[j].clone(); |
||||||
|
} |
||||||
|
} |
||||||
|
boneTracks[i] = new BoneTrack(sourceTracks[i].getTargetBoneIndex(), sourceTracks[i].getTimes(),//times do not change, no need to clone them,
|
||||||
|
translations, rotations, scales); |
||||||
|
} |
||||||
|
result.setTracks(boneTracks); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method merges two skeletons into one. I assume that each skeleton's 0-indexed bone is objectAnimationBone so |
||||||
|
* only one such bone should be placed in the result |
||||||
|
* @param s1 |
||||||
|
* first skeleton |
||||||
|
* @param s2 |
||||||
|
* second skeleton |
||||||
|
* @return merged skeleton |
||||||
|
*/ |
||||||
|
protected Skeleton merge(Skeleton s1, Skeleton s2) { |
||||||
|
List<Bone> bones = new ArrayList<Bone>(s1.getBoneCount() + s2.getBoneCount()); |
||||||
|
for(int i = 0; i < s1.getBoneCount(); ++i) { |
||||||
|
bones.add(s1.getBone(i)); |
||||||
|
} |
||||||
|
for(int i = 1; i < s2.getBoneCount(); ++i) {//ommit objectAnimationBone
|
||||||
|
bones.add(s2.getBone(i)); |
||||||
|
} |
||||||
|
return new Skeleton(bones.toArray(new Bone[bones.size()])); |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,407 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.BoneAnimation; |
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.light.DirectionalLight; |
||||||
|
import com.jme3.light.Light; |
||||||
|
import com.jme3.light.PointLight; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.renderer.Camera; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial.CullHint; |
||||||
|
import com.jme3.scene.plugins.blender.data.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Ipo; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Modifier; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository.LoadedFeatureDataType; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
import com.jme3.scene.plugins.ogre.AnimData; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that is used in object calculations. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class ObjectHelper extends AbstractBlenderHelper { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); |
||||||
|
|
||||||
|
protected static final int OBJECT_TYPE_EMPTY = 0; |
||||||
|
protected static final int OBJECT_TYPE_MESH = 1; |
||||||
|
protected static final int OBJECT_TYPE_CURVE = 2; |
||||||
|
protected static final int OBJECT_TYPE_SURF = 3; |
||||||
|
protected static final int OBJECT_TYPE_TEXT = 4; |
||||||
|
protected static final int OBJECT_TYPE_METABALL = 5; |
||||||
|
protected static final int OBJECT_TYPE_LAMP = 10; |
||||||
|
protected static final int OBJECT_TYPE_CAMERA = 11; |
||||||
|
protected static final int OBJECT_TYPE_WAVE = 21; |
||||||
|
protected static final int OBJECT_TYPE_LATTICE = 22; |
||||||
|
protected static final int OBJECT_TYPE_ARMATURE = 25; |
||||||
|
|
||||||
|
/** This variable indicates if the Y asxis is the UP axis or not. */ |
||||||
|
protected boolean fixUpAxis; |
||||||
|
/** Quaternion used to rotate data when Y is up axis. */ |
||||||
|
protected Quaternion upAxisRotationQuaternion; |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in |
||||||
|
* different blender versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public ObjectHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the Y is UP axis. By default the UP axis is Z (just like in blender). |
||||||
|
* @param fixUpAxis |
||||||
|
* a variable that indicates if the Y asxis is the UP axis or not |
||||||
|
*/ |
||||||
|
public void setyIsUpAxis(boolean fixUpAxis) { |
||||||
|
this.fixUpAxis = fixUpAxis; |
||||||
|
if(fixUpAxis) { |
||||||
|
upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads the given structure and createn an object that represents the data. |
||||||
|
* @param objectStructure |
||||||
|
* the object's structure |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return blener's object representation |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when the given data is inapropriate |
||||||
|
*/ |
||||||
|
public Object toObject(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
Object loadedResult = dataRepository.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
if(loadedResult != null) { |
||||||
|
return loadedResult; |
||||||
|
} |
||||||
|
|
||||||
|
dataRepository.pushParent(objectStructure); |
||||||
|
|
||||||
|
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); |
||||||
|
ModifierHelper modifierHelper = dataRepository.getHelper(ModifierHelper.class); |
||||||
|
ArmatureHelper armatureHelper = dataRepository.getHelper(ArmatureHelper.class); |
||||||
|
ConstraintHelper constraintHelper = dataRepository.getHelper(ConstraintHelper.class); |
||||||
|
|
||||||
|
//get object data
|
||||||
|
int type = ((Number)objectStructure.getFieldValue("type")).intValue(); |
||||||
|
String name = objectStructure.getName(); |
||||||
|
LOGGER.log(Level.INFO, "Loading obejct: {0}", name); |
||||||
|
|
||||||
|
//reading modifiers
|
||||||
|
modifierHelper.readModifiers(objectStructure, dataRepository); |
||||||
|
Modifier objectAnimationModifier = objectHelper.readObjectAnimation(objectStructure, dataRepository); |
||||||
|
|
||||||
|
//loading constraints connected with this object
|
||||||
|
constraintHelper.loadConstraints(objectStructure, dataRepository); |
||||||
|
|
||||||
|
int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue(); |
||||||
|
boolean visible = (restrictflag & 0x01) != 0; |
||||||
|
Object result = null; |
||||||
|
|
||||||
|
Pointer pParent = (Pointer)objectStructure.getFieldValue("parent"); |
||||||
|
Object parent = dataRepository.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); |
||||||
|
if(parent == null && !pParent.isNull()) { |
||||||
|
Structure parentStructure = pParent.fetchData(dataRepository.getInputStream()).get(0);//TODO: moze byc wiecej rodzicow
|
||||||
|
parent = this.toObject(parentStructure, dataRepository); |
||||||
|
} |
||||||
|
|
||||||
|
Transform t = objectHelper.getTransformation(objectStructure); |
||||||
|
|
||||||
|
try { |
||||||
|
switch(type) { |
||||||
|
case OBJECT_TYPE_EMPTY: |
||||||
|
LOGGER.log(Level.INFO, "Importing empty."); |
||||||
|
Node empty = new Node(name); |
||||||
|
empty.setLocalTransform(t); |
||||||
|
result = empty; |
||||||
|
break; |
||||||
|
case OBJECT_TYPE_MESH: |
||||||
|
LOGGER.log(Level.INFO, "Importing mesh."); |
||||||
|
Node node = new Node(name); |
||||||
|
node.setCullHint(visible ? CullHint.Always : CullHint.Inherit); |
||||||
|
|
||||||
|
//reading mesh
|
||||||
|
MeshHelper meshHelper = dataRepository.getHelper(MeshHelper.class); |
||||||
|
Pointer pMesh = (Pointer)objectStructure.getFieldValue("data"); |
||||||
|
List<Structure> meshesArray = pMesh.fetchData(dataRepository.getInputStream()); |
||||||
|
List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), dataRepository); |
||||||
|
for(Geometry geometry : geometries) { |
||||||
|
node.attachChild(geometry); |
||||||
|
} |
||||||
|
node.setLocalTransform(t); |
||||||
|
|
||||||
|
//applying all modifiers
|
||||||
|
List<Modifier> modifiers = dataRepository.getModifiers(objectStructure.getOldMemoryAddress(), null); |
||||||
|
for(Modifier modifier : modifiers) { |
||||||
|
modifierHelper.applyModifier(node, modifier, dataRepository); |
||||||
|
} |
||||||
|
//adding object animation modifier
|
||||||
|
if(objectAnimationModifier != null) { |
||||||
|
node = modifierHelper.applyModifier(node, objectAnimationModifier, dataRepository); |
||||||
|
} |
||||||
|
|
||||||
|
//setting the parent
|
||||||
|
if(parent instanceof Node) { |
||||||
|
((Node)parent).attachChild(node); |
||||||
|
} |
||||||
|
node.updateModelBound();//I prefer do calculate bounding box here than read it from the file
|
||||||
|
result = node; |
||||||
|
break; |
||||||
|
case OBJECT_TYPE_SURF: |
||||||
|
case OBJECT_TYPE_CURVE: |
||||||
|
LOGGER.log(Level.INFO, "Importing curve/nurb."); |
||||||
|
Pointer pCurve = (Pointer)objectStructure.getFieldValue("data"); |
||||||
|
if(!pCurve.isNull()) { |
||||||
|
CurvesHelper curvesHelper = dataRepository.getHelper(CurvesHelper.class); |
||||||
|
Structure curveData = pCurve.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
List<Geometry> curves = curvesHelper.toCurve(curveData, dataRepository); |
||||||
|
result = new Node(name); |
||||||
|
for(Geometry curve : curves) { |
||||||
|
((Node)result).attachChild(curve); |
||||||
|
} |
||||||
|
((Node)result).setLocalTransform(t); |
||||||
|
} |
||||||
|
break; |
||||||
|
case OBJECT_TYPE_LAMP: |
||||||
|
LOGGER.log(Level.INFO, "Importing lamp."); |
||||||
|
Pointer pLamp = (Pointer)objectStructure.getFieldValue("data"); |
||||||
|
if(!pLamp.isNull()) { |
||||||
|
LightHelper lightHelper = dataRepository.getHelper(LightHelper.class); |
||||||
|
List<Structure> lampsArray = pLamp.fetchData(dataRepository.getInputStream()); |
||||||
|
Light light = lightHelper.toLight(lampsArray.get(0), dataRepository); |
||||||
|
if(light!=null) { |
||||||
|
light.setName(name); |
||||||
|
} |
||||||
|
if(light instanceof PointLight) { |
||||||
|
((PointLight)light).setPosition(t.getTranslation()); |
||||||
|
} else if(light instanceof DirectionalLight) { |
||||||
|
Quaternion quaternion = t.getRotation(); |
||||||
|
Vector3f[] axes = new Vector3f[3]; |
||||||
|
quaternion.toAxes(axes); |
||||||
|
((DirectionalLight)light).setDirection(axes[2].negate());//-Z is the direction axis of area lamp in blender
|
||||||
|
} else { |
||||||
|
LOGGER.log(Level.WARNING, "Unknown type of light: {0}", light); |
||||||
|
} |
||||||
|
result = light; |
||||||
|
} |
||||||
|
break; |
||||||
|
case OBJECT_TYPE_CAMERA: |
||||||
|
Pointer pCamera = (Pointer)objectStructure.getFieldValue("data"); |
||||||
|
if(!pCamera.isNull()) { |
||||||
|
CameraHelper cameraHelper = dataRepository.getHelper(CameraHelper.class); |
||||||
|
List<Structure> camerasArray = pCamera.fetchData(dataRepository.getInputStream()); |
||||||
|
Camera camera = cameraHelper.toCamera(camerasArray.get(0)); |
||||||
|
camera.setLocation(t.getTranslation()); |
||||||
|
camera.setRotation(t.getRotation()); |
||||||
|
result = camera; |
||||||
|
} |
||||||
|
break; |
||||||
|
case OBJECT_TYPE_ARMATURE: |
||||||
|
LOGGER.log(Level.INFO, "Importing armature."); |
||||||
|
Pointer pArmature = (Pointer)objectStructure.getFieldValue("data"); |
||||||
|
List<Structure> armaturesArray = pArmature.fetchData(dataRepository.getInputStream());//TODO: moze byc wiecej???
|
||||||
|
result = armatureHelper.toArmature(armaturesArray.get(0), dataRepository); |
||||||
|
break; |
||||||
|
default: |
||||||
|
LOGGER.log(Level.WARNING, "Unknown object type: {0}", type); |
||||||
|
} |
||||||
|
} finally { |
||||||
|
dataRepository.popParent(); |
||||||
|
} |
||||||
|
if(result != null) { |
||||||
|
dataRepository.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method calculates local transformation for the object. Parentage is taken under consideration. |
||||||
|
* @param objectStructure |
||||||
|
* the object's structure |
||||||
|
* @return objects transformation relative to its parent |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public Transform getTransformation(Structure objectStructure) { |
||||||
|
DynamicArray<Number> loc = (DynamicArray<Number>)objectStructure.getFieldValue("loc"); |
||||||
|
DynamicArray<Number> size = (DynamicArray<Number>)objectStructure.getFieldValue("size"); |
||||||
|
DynamicArray<Number> rot = (DynamicArray<Number>)objectStructure.getFieldValue("rot"); |
||||||
|
|
||||||
|
Pointer parent = (Pointer) objectStructure.getFieldValue("parent"); |
||||||
|
Matrix4f parentInv = parent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv"); |
||||||
|
|
||||||
|
Matrix4f globalMatrix = new Matrix4f(); |
||||||
|
globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); |
||||||
|
globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue())); |
||||||
|
Matrix4f localMatrix = parentInv.mult(globalMatrix); |
||||||
|
|
||||||
|
Vector3f translation = localMatrix.toTranslationVector(); |
||||||
|
Quaternion rotation = localMatrix.toRotationQuat(); |
||||||
|
//getting the scale
|
||||||
|
float scaleX = (float) Math.sqrt(parentInv.m00 * parentInv.m00 + parentInv.m10 * parentInv.m10 + parentInv.m20 * parentInv.m20); |
||||||
|
float scaleY = (float) Math.sqrt(parentInv.m01 * parentInv.m01 + parentInv.m11 * parentInv.m11 + parentInv.m21 * parentInv.m21); |
||||||
|
float scaleZ = (float) Math.sqrt(parentInv.m02 * parentInv.m02 + parentInv.m12 * parentInv.m12 + parentInv.m22 * parentInv.m22); |
||||||
|
Vector3f scale = new Vector3f(size.get(0).floatValue() * scaleX, |
||||||
|
size.get(1).floatValue() * scaleY, |
||||||
|
size.get(2).floatValue() * scaleZ); |
||||||
|
if(fixUpAxis) { |
||||||
|
float y = translation.y; |
||||||
|
translation.y = translation.z; |
||||||
|
translation.z = y; |
||||||
|
rotation.multLocal(this.upAxisRotationQuaternion); |
||||||
|
} |
||||||
|
Transform t = new Transform(translation, rotation); |
||||||
|
t.setScale(scale); |
||||||
|
return t; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the transformation matrix of the given object structure. |
||||||
|
* @param objectStructure |
||||||
|
* the structure with object's data |
||||||
|
* @return object's transformation matrix |
||||||
|
*/ |
||||||
|
public Matrix4f getTransformationMatrix(Structure objectStructure) { |
||||||
|
return this.getMatrix(objectStructure, "obmat"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the matrix of a given name for the given object structure. |
||||||
|
* @param objectStructure |
||||||
|
* the structure with object's data |
||||||
|
* @param matrixName |
||||||
|
* the name of the matrix structure |
||||||
|
* @return object's matrix |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
protected Matrix4f getMatrix(Structure objectStructure, String matrixName) { |
||||||
|
Matrix4f result = new Matrix4f(); |
||||||
|
DynamicArray<Number> obmat = (DynamicArray<Number>)objectStructure.getFieldValue(matrixName); |
||||||
|
for(int i = 0; i < 4; ++i) { |
||||||
|
for(int j = 0; j < 4; ++j) { |
||||||
|
result.set(i, j, obmat.get(j, i).floatValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads animation of the object itself (without bones) and stores it as an ArmatureModifierData |
||||||
|
* modifier. The animation is returned as a modifier. It should be later applied regardless other modifiers. The |
||||||
|
* reason for this is that object may not have modifiers added but it's animation should be working. |
||||||
|
* @param objectStructure |
||||||
|
* the structure of the object |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @return animation modifier is returned, it should be separately applied when the object is loaded |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow corrupted |
||||||
|
*/ |
||||||
|
public Modifier readObjectAnimation(Structure objectStructure, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
Pointer pIpo = (Pointer)objectStructure.getFieldValue("ipo"); |
||||||
|
if(!pIpo.isNull()) { |
||||||
|
//check if there is an action name connected with this ipo
|
||||||
|
String objectAnimationName = null; |
||||||
|
List<FileBlockHeader> actionBlocks = dataRepository.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); |
||||||
|
for(FileBlockHeader actionBlock : actionBlocks) { |
||||||
|
Structure action = actionBlock.getStructure(dataRepository); |
||||||
|
List<Structure> actionChannels = ((Structure)action.getFieldValue("chanbase")).evaluateListBase(dataRepository); |
||||||
|
if(actionChannels.size() == 1) {//object's animtion action has only one channel
|
||||||
|
Pointer pChannelIpo = (Pointer)actionChannels.get(0).getFieldValue("ipo"); |
||||||
|
if(pChannelIpo.equals(pIpo)) { |
||||||
|
objectAnimationName = action.getName(); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
String objectName = objectStructure.getName(); |
||||||
|
if(objectAnimationName == null) {//set the object's animation name to object's name
|
||||||
|
objectAnimationName = objectName; |
||||||
|
} |
||||||
|
|
||||||
|
IpoHelper ipoHelper = dataRepository.getHelper(IpoHelper.class); |
||||||
|
Structure ipoStructure = pIpo.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
Ipo ipo = ipoHelper.createIpo(ipoStructure, dataRepository); |
||||||
|
int[] animationFrames = dataRepository.getBlenderKey().getAnimationFrames(objectName, objectAnimationName); |
||||||
|
if(animationFrames == null) {//if the name was created here there are no frames set for the animation
|
||||||
|
animationFrames = new int[] {1, ipo.getLastFrame()}; |
||||||
|
} |
||||||
|
int fps = dataRepository.getBlenderKey().getFps(); |
||||||
|
float start = (float)animationFrames[0] / (float)fps; |
||||||
|
float stop = (float)animationFrames[1] / (float)fps; |
||||||
|
|
||||||
|
//calculating track for the only bone in this skeleton
|
||||||
|
BoneTrack[] tracks = new BoneTrack[1]; |
||||||
|
tracks[0] = ipo.calculateTrack(0, animationFrames[0], animationFrames[1], fps); |
||||||
|
|
||||||
|
BoneAnimation boneAnimation = new BoneAnimation(objectAnimationName, stop - start); |
||||||
|
boneAnimation.setTracks(tracks); |
||||||
|
ArrayList<BoneAnimation> animations = new ArrayList<BoneAnimation>(1); |
||||||
|
animations.add(boneAnimation); |
||||||
|
|
||||||
|
//preparing the object's bone
|
||||||
|
Transform t = this.getTransformation(objectStructure); |
||||||
|
Bone bone = new Bone(null); |
||||||
|
bone.setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale()); |
||||||
|
|
||||||
|
return new Modifier(Modifier.ARMATURE_MODIFIER_DATA, new AnimData(new Skeleton(new Bone[] {bone}), animations), null); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void clearState() { |
||||||
|
fixUpAxis = false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,118 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.helpers.v249; |
||||||
|
|
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.effect.EmitterMeshConvexHullShape; |
||||||
|
import com.jme3.effect.EmitterMeshFaceShape; |
||||||
|
import com.jme3.effect.EmitterMeshVertexShape; |
||||||
|
import com.jme3.effect.ParticleEmitter; |
||||||
|
import com.jme3.effect.ParticleMesh.Type; |
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.utils.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
|
||||||
|
public class ParticlesHelper extends AbstractBlenderHelper { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName()); |
||||||
|
|
||||||
|
// part->type
|
||||||
|
public static final int PART_EMITTER = 0; |
||||||
|
public static final int PART_REACTOR = 1; |
||||||
|
public static final int PART_HAIR = 2; |
||||||
|
public static final int PART_FLUID = 3; |
||||||
|
|
||||||
|
// part->flag
|
||||||
|
public static final int PART_REACT_STA_END =1; |
||||||
|
public static final int PART_REACT_MULTIPLE =2; |
||||||
|
public static final int PART_LOOP =4; |
||||||
|
//public static final int PART_LOOP_INSTANT =8;
|
||||||
|
public static final int PART_HAIR_GEOMETRY =16; |
||||||
|
public static final int PART_UNBORN =32; //show unborn particles
|
||||||
|
public static final int PART_DIED =64; //show died particles
|
||||||
|
public static final int PART_TRAND =128; |
||||||
|
public static final int PART_EDISTR =256; // particle/face from face areas
|
||||||
|
public static final int PART_STICKY =512; //collided particles can stick to collider
|
||||||
|
public static final int PART_DIE_ON_COL =1<<12; |
||||||
|
public static final int PART_SIZE_DEFL =1<<13; // swept sphere deflections
|
||||||
|
public static final int PART_ROT_DYN =1<<14; // dynamic rotation
|
||||||
|
public static final int PART_SIZEMASS =1<<16; |
||||||
|
public static final int PART_ABS_LENGTH =1<<15; |
||||||
|
public static final int PART_ABS_TIME =1<<17; |
||||||
|
public static final int PART_GLOB_TIME =1<<18; |
||||||
|
public static final int PART_BOIDS_2D =1<<19; |
||||||
|
public static final int PART_BRANCHING =1<<20; |
||||||
|
public static final int PART_ANIM_BRANCHING =1<<21; |
||||||
|
public static final int PART_SELF_EFFECT =1<<22; |
||||||
|
public static final int PART_SYMM_BRANCHING =1<<24; |
||||||
|
public static final int PART_HAIR_BSPLINE =1024; |
||||||
|
public static final int PART_GRID_INVERT =1<<26; |
||||||
|
public static final int PART_CHILD_EFFECT =1<<27; |
||||||
|
public static final int PART_CHILD_SEAMS =1<<28; |
||||||
|
public static final int PART_CHILD_RENDER =1<<29; |
||||||
|
public static final int PART_CHILD_GUIDE =1<<30; |
||||||
|
|
||||||
|
// part->from
|
||||||
|
public static final int PART_FROM_VERT =0; |
||||||
|
public static final int PART_FROM_FACE =1; |
||||||
|
public static final int PART_FROM_VOLUME =2; |
||||||
|
public static final int PART_FROM_PARTICLE =3; |
||||||
|
public static final int PART_FROM_CHILD =4; |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in |
||||||
|
* different blender versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public ParticlesHelper(String blenderVersion) { |
||||||
|
super(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public ParticleEmitter toParticleEmitter(Structure particleSystem, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
ParticleEmitter result = null; |
||||||
|
Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part"); |
||||||
|
if(!pParticleSettings.isNull()) { |
||||||
|
Structure particleSettings = pParticleSettings.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue(); |
||||||
|
result = new ParticleEmitter(particleSettings.getName(), Type.Triangle, totPart); |
||||||
|
|
||||||
|
//setting the emitters shape (the shapes meshes will be set later during modifier applying operation)
|
||||||
|
int from = ((Number)particleSettings.getFieldValue("from")).intValue(); |
||||||
|
switch(from) { |
||||||
|
case PART_FROM_VERT: |
||||||
|
result.setShape(new EmitterMeshVertexShape()); |
||||||
|
break; |
||||||
|
case PART_FROM_FACE: |
||||||
|
result.setShape(new EmitterMeshFaceShape()); |
||||||
|
break; |
||||||
|
case PART_FROM_VOLUME: |
||||||
|
result.setShape(new EmitterMeshConvexHullShape()); |
||||||
|
break; |
||||||
|
default: |
||||||
|
LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter): " + from); |
||||||
|
} |
||||||
|
|
||||||
|
//reading acceleration
|
||||||
|
DynamicArray<Number> acc = (DynamicArray<Number>) particleSettings.getFieldValue("acc"); |
||||||
|
result.setInitialVelocity(new Vector3f(acc.get(0).floatValue(), acc.get(1).floatValue(), acc.get(2).floatValue())); |
||||||
|
result.setGravity(0);//by default gravity is set to 0.1f so we need to disable it completely
|
||||||
|
// 2x2 texture animation
|
||||||
|
result.setImagesX(2); |
||||||
|
result.setImagesY(2); |
||||||
|
result.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f)); // red
|
||||||
|
result.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow
|
||||||
|
result.setStartSize(1.5f); |
||||||
|
result.setEndSize(0.1f); |
||||||
|
|
||||||
|
result.setLowLife(0.5f); |
||||||
|
result.setHighLife(3f); |
||||||
|
result.setVelocityVariation(0.3f); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,137 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.structures; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DynamicArray; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize |
||||||
|
* floating point operations errors. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class BezierCurve { |
||||||
|
public static final int X_VALUE = 0; |
||||||
|
public static final int Y_VALUE = 1; |
||||||
|
public static final int Z_VALUE = 2; |
||||||
|
|
||||||
|
/** |
||||||
|
* The type of the curve. Describes the data it modifies. |
||||||
|
* Used in ipos calculations. |
||||||
|
*/ |
||||||
|
private int type; |
||||||
|
/** The dimension of the curve. */ |
||||||
|
private int dimension; |
||||||
|
/** A table of the bezier points. */ |
||||||
|
private float[][][] bezierPoints; |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) { |
||||||
|
if(dimension != 2 && dimension != 3) { |
||||||
|
throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!"); |
||||||
|
} |
||||||
|
this.type = type; |
||||||
|
this.dimension = dimension; |
||||||
|
//first index of the bezierPoints table has the length of triples amount
|
||||||
|
//the second index points to a table od three points of a bezier triple (handle, point, handle)
|
||||||
|
//the third index specifies the coordinates of the specific point in a bezier triple
|
||||||
|
bezierPoints = new float[bezTriples.size()][3][dimension]; |
||||||
|
int i = 0, j, k; |
||||||
|
for(Structure bezTriple : bezTriples) { |
||||||
|
DynamicArray<Number> vec = (DynamicArray<Number>)bezTriple.getFieldValue("vec"); |
||||||
|
for(j = 0; j < 3; ++j) { |
||||||
|
for(k = 0; k < dimension; ++k) { |
||||||
|
bezierPoints[i][j][k] = vec.get(j, k).floatValue(); |
||||||
|
} |
||||||
|
} |
||||||
|
++i; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method evaluates the data for the specified frame. The Y value is returned. |
||||||
|
* @param frame |
||||||
|
* the frame for which the value is being calculated |
||||||
|
* @param valuePart |
||||||
|
* this param specifies wheather we should return the X, Y or Z part of the result value; it should have |
||||||
|
* one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result |
||||||
|
* Z_VALUE - the Z factor of the result |
||||||
|
* @return the value of the curve |
||||||
|
*/ |
||||||
|
public float evaluate(int frame, int valuePart) { |
||||||
|
for(int i = 0; i < bezierPoints.length - 1; ++i) { |
||||||
|
if(frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) { |
||||||
|
float t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]); |
||||||
|
float oneMinusT = 1.0f - t; |
||||||
|
float oneMinusT2 = oneMinusT * oneMinusT; |
||||||
|
float t2 = t * t; |
||||||
|
return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t; |
||||||
|
} |
||||||
|
} |
||||||
|
if(frame < bezierPoints[0][1][0]) { |
||||||
|
return bezierPoints[0][1][1]; |
||||||
|
} else { //frame>bezierPoints[bezierPoints.length-1][1][0]
|
||||||
|
return bezierPoints[bezierPoints.length - 1][1][1]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the frame where last bezier triple center point of the bezier curve is located. |
||||||
|
* @return the frame number of the last defined bezier triple point for the curve |
||||||
|
*/ |
||||||
|
public int getLastFrame() { |
||||||
|
return (int)bezierPoints[bezierPoints.length - 1][1][0]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the type of the bezier curve. The type describes the parameter that this curve modifies |
||||||
|
* (ie. LocationX or rotationW of the feature). |
||||||
|
* @return the type of the bezier curve |
||||||
|
*/ |
||||||
|
public int getType() { |
||||||
|
return type; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a list of control points for this curve. |
||||||
|
* @return a list of control points for this curve. |
||||||
|
*/ |
||||||
|
public List<Vector3f> getControlPoints() { |
||||||
|
List<Vector3f> controlPoints = new ArrayList<Vector3f>(bezierPoints.length * 3); |
||||||
|
for(int i = 0;i<bezierPoints.length;++i) { |
||||||
|
controlPoints.add(new Vector3f(bezierPoints[i][0][0], bezierPoints[i][0][1], bezierPoints[i][0][2])); |
||||||
|
controlPoints.add(new Vector3f(bezierPoints[i][1][0], bezierPoints[i][1][1], bezierPoints[i][1][2])); |
||||||
|
controlPoints.add(new Vector3f(bezierPoints[i][2][0], bezierPoints[i][2][1], bezierPoints[i][2][2])); |
||||||
|
} |
||||||
|
return controlPoints; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
StringBuilder sb = new StringBuilder("Bezier curve: ").append(type).append('\n'); |
||||||
|
for(int i = 0; i < bezierPoints.length; ++i) { |
||||||
|
sb.append(this.toStringBezTriple(i)).append('\n'); |
||||||
|
} |
||||||
|
return sb.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method converts the bezier triple of a specified index into text. |
||||||
|
* @param tripleIndex |
||||||
|
* index of the triple |
||||||
|
* @return text representation of the triple |
||||||
|
*/ |
||||||
|
private String toStringBezTriple(int tripleIndex) { |
||||||
|
if(this.dimension==2) { |
||||||
|
return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" |
||||||
|
+ bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" |
||||||
|
+ bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]"; |
||||||
|
} else { |
||||||
|
return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" |
||||||
|
+ bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" |
||||||
|
+ bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,162 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.structures; |
||||||
|
|
||||||
|
import com.jme3.animation.BoneAnimation; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.utils.DataRepository; |
||||||
|
import com.jme3.scene.plugins.blender.utils.Pointer; |
||||||
|
|
||||||
|
/** |
||||||
|
* The implementation of a constraint. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class Constraint { |
||||||
|
/** The type of this constraint. */ |
||||||
|
private final ConstraintType type; |
||||||
|
/** The name of this constraint. */ |
||||||
|
private final String name; |
||||||
|
/** The old memory address of the constraint's owner. */ |
||||||
|
private final Long boneOMA; |
||||||
|
|
||||||
|
private final Space ownerSpace; |
||||||
|
|
||||||
|
private final Space targetSpace; |
||||||
|
/** The structure with constraint's data. */ |
||||||
|
private final Structure data; |
||||||
|
/** The ipo object defining influence. */ |
||||||
|
private final Ipo ipo; |
||||||
|
/** The influence function of this constraint. */ |
||||||
|
private final AbstractInfluenceFunction influenceFunction; |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor creates the constraint instance. |
||||||
|
* @param constraintStructure |
||||||
|
* the constraint's structure (bConstraint clss in blender 2.49). |
||||||
|
* @param influenceFunction |
||||||
|
* the constraint's influence function (taken from ConstraintHelper) |
||||||
|
* @param boneOMA |
||||||
|
* the old memory address of the constraint owner |
||||||
|
* @param influenceIpo |
||||||
|
* the ipo curve of the influence factor |
||||||
|
* @param dataRepository |
||||||
|
* the data repository |
||||||
|
* @throws BlenderFileException |
||||||
|
*/ |
||||||
|
public Constraint(Structure constraintStructure, AbstractInfluenceFunction influenceFunction, Long boneOMA, Space ownerSpace, Space targetSpace, Ipo influenceIpo, DataRepository dataRepository) throws BlenderFileException { |
||||||
|
if(influenceFunction == null) { |
||||||
|
throw new IllegalArgumentException("Influence function is not defined!"); |
||||||
|
} |
||||||
|
Pointer pData = (Pointer)constraintStructure.getFieldValue("data"); |
||||||
|
if(!pData.isNull()) { |
||||||
|
data = pData.fetchData(dataRepository.getInputStream()).get(0); |
||||||
|
} else { |
||||||
|
throw new BlenderFileException("The constraint has no data specified!"); |
||||||
|
} |
||||||
|
this.boneOMA = boneOMA; |
||||||
|
this.type = ConstraintType.valueOf(((Number)constraintStructure.getFieldValue("type")).intValue()); |
||||||
|
this.name = constraintStructure.getFieldValue("name").toString(); |
||||||
|
this.ownerSpace = ownerSpace; |
||||||
|
this.targetSpace = targetSpace; |
||||||
|
this.ipo = influenceIpo; |
||||||
|
this.influenceFunction = influenceFunction; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the name of the constraint. |
||||||
|
* @return the name of the constraint |
||||||
|
*/ |
||||||
|
public String getName() { |
||||||
|
return name; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the old memoty address of the bone this constraint affects. |
||||||
|
* @return the old memory address of the bone this constraint affects |
||||||
|
*/ |
||||||
|
public Long getBoneOMA() { |
||||||
|
return boneOMA; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns owner's transform space. |
||||||
|
* @return owner's transform space |
||||||
|
*/ |
||||||
|
public Space getOwnerSpace() { |
||||||
|
return ownerSpace; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns target's transform space. |
||||||
|
* @return target's transform space |
||||||
|
*/ |
||||||
|
public Space getTargetSpace() { |
||||||
|
return targetSpace; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the type of the constraint. |
||||||
|
* @return the type of the constraint |
||||||
|
*/ |
||||||
|
public ConstraintType getType() { |
||||||
|
return type; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the constraint's data structure. |
||||||
|
* @return the constraint's data structure |
||||||
|
*/ |
||||||
|
public Structure getData() { |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the constraint's influcence curve. |
||||||
|
* @return the constraint's influcence curve |
||||||
|
*/ |
||||||
|
public Ipo getIpo() { |
||||||
|
return ipo; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method affects the bone animation tracks for the given skeleton. |
||||||
|
* @param skeleton |
||||||
|
* the skeleton containing the affected bones by constraint |
||||||
|
* @param boneAnimation |
||||||
|
* the bone animation baked traces |
||||||
|
* @param constraint |
||||||
|
* the constraint |
||||||
|
*/ |
||||||
|
public void affectAnimation(Skeleton skeleton, BoneAnimation boneAnimation) { |
||||||
|
influenceFunction.affectAnimation(skeleton, boneAnimation, this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The space of target or owner transformation. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public static enum Space { |
||||||
|
CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID; |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the enum instance when given the appropriate value from the blend file. |
||||||
|
* @param c |
||||||
|
* the blender's value of the space modifier |
||||||
|
* @return the scape enum instance |
||||||
|
*/ |
||||||
|
public static Space valueOf(byte c) { |
||||||
|
switch(c) { |
||||||
|
case 0: |
||||||
|
return CONSTRAINT_SPACE_WORLD; |
||||||
|
case 1: |
||||||
|
return CONSTRAINT_SPACE_LOCAL; |
||||||
|
case 2: |
||||||
|
return CONSTRAINT_SPACE_POSE; |
||||||
|
case 3: |
||||||
|
return CONSTRAINT_SPACE_PARLOCAL; |
||||||
|
default: |
||||||
|
return CONSTRAINT_SPACE_INVALID; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.structures; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constraint types. Definitions taken from blender sources, file: DNA_constraint_types.h. Constraint id's the same as |
||||||
|
* used in blender. The constraints might have duplicated type ids, depending on the blender version. The purpose of |
||||||
|
* this enum is to combine class name and the constraint type (id). |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public enum ConstraintType { |
||||||
|
/* Invalid/legacy constraint */ |
||||||
|
CONSTRAINT_TYPE_NULL(0, "bNullConstraint"), |
||||||
|
/* Unimplemented non longer :) - during constraints recode, Aligorith */ |
||||||
|
CONSTRAINT_TYPE_CHILDOF(1, "bChildOfConstraint"), |
||||||
|
CONSTRAINT_TYPE_KINEMATIC(3, "bKinematicConstraint"), |
||||||
|
CONSTRAINT_TYPE_FOLLOWPATH(4, "bFollowPathConstraint"), |
||||||
|
/* Unimplemented no longer :) - Aligorith */ |
||||||
|
CONSTRAINT_TYPE_ROTLIMIT(5, "bRotLimitConstraint"), |
||||||
|
/* Unimplemented no longer :) - Aligorith */ |
||||||
|
CONSTRAINT_TYPE_LOCLIMIT(6, "bLocLimitConstraint"), |
||||||
|
/* Unimplemented no longer :) - Aligorith */ |
||||||
|
CONSTRAINT_TYPE_SIZELIMIT(7, "bSizeLimitConstraint"), |
||||||
|
CONSTRAINT_TYPE_ROTLIKE(8, "bRotateLikeConstraint"), |
||||||
|
CONSTRAINT_TYPE_LOCLIKE(9, "bLocateLikeConstraint"), |
||||||
|
CONSTRAINT_TYPE_SIZELIKE(10, "bSizeLikeConstraint"), |
||||||
|
/* Unimplemented no longer :) - Aligorith. Scripts */ |
||||||
|
CONSTRAINT_TYPE_PYTHON(11, "bPythonConstraint"), |
||||||
|
CONSTRAINT_TYPE_ACTION(12, "bActionConstraint"), |
||||||
|
/* New Tracking constraint that locks an axis in place - theeth */ |
||||||
|
CONSTRAINT_TYPE_LOCKTRACK(13, "bLockTrackConstraint"), |
||||||
|
/* limit distance */ |
||||||
|
CONSTRAINT_TYPE_DISTLIMIT(14, "bDistLimitConstraint"), |
||||||
|
/* claiming this to be mine :) is in tuhopuu bjornmose */ |
||||||
|
CONSTRAINT_TYPE_STRETCHTO(15, "bStretchToConstraint"), |
||||||
|
/* floor constraint */ |
||||||
|
CONSTRAINT_TYPE_MINMAX(16, "bMinMaxConstraint"), |
||||||
|
/* rigidbody constraint */ |
||||||
|
CONSTRAINT_TYPE_RIGIDBODYJOINT(17, "bRigidBodyConstraint"), |
||||||
|
/* clampto constraint */ |
||||||
|
CONSTRAINT_TYPE_CLAMPTO(18, "bClampToConstraint"), |
||||||
|
/* transformation (loc/rot/size -> loc/rot/size) constraint */ |
||||||
|
CONSTRAINT_TYPE_TRANSFORM(19, "bTransformConstraint"), |
||||||
|
/* shrinkwrap (loc/rot) constraint */ |
||||||
|
CONSTRAINT_TYPE_SHRINKWRAP(20, "bShrinkwrapConstraint"); |
||||||
|
|
||||||
|
/** The constraint's id (in blender known as 'type'). */ |
||||||
|
private int constraintId; |
||||||
|
/** The name of constraint class used by blender. */ |
||||||
|
private String className; |
||||||
|
/** The map containing class names and types of constraints. */ |
||||||
|
private static Map<String, ConstraintType> typesMap = new HashMap<String, ConstraintType>(ConstraintType.values().length); |
||||||
|
/** The map containing class names and types of constraints. */ |
||||||
|
private static Map<Integer, ConstraintType> idsMap = new HashMap<Integer, ConstraintType>(ConstraintType.values().length); |
||||||
|
static { |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_NULL.constraintId), CONSTRAINT_TYPE_NULL); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_CHILDOF.constraintId), CONSTRAINT_TYPE_CHILDOF); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_KINEMATIC.constraintId), CONSTRAINT_TYPE_KINEMATIC); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_FOLLOWPATH.constraintId), CONSTRAINT_TYPE_FOLLOWPATH); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ROTLIMIT.constraintId), CONSTRAINT_TYPE_ROTLIMIT); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCLIMIT.constraintId), CONSTRAINT_TYPE_LOCLIMIT); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SIZELIMIT.constraintId), CONSTRAINT_TYPE_SIZELIMIT); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ROTLIKE.constraintId), CONSTRAINT_TYPE_ROTLIKE); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCLIKE.constraintId), CONSTRAINT_TYPE_LOCLIKE); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SIZELIKE.constraintId), CONSTRAINT_TYPE_SIZELIKE); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_PYTHON.constraintId), CONSTRAINT_TYPE_PYTHON); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_ACTION.constraintId), CONSTRAINT_TYPE_ACTION); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_LOCKTRACK.constraintId), CONSTRAINT_TYPE_LOCKTRACK); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_DISTLIMIT.constraintId), CONSTRAINT_TYPE_DISTLIMIT); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_STRETCHTO.constraintId), CONSTRAINT_TYPE_STRETCHTO); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_MINMAX.constraintId), CONSTRAINT_TYPE_MINMAX); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_RIGIDBODYJOINT.constraintId), CONSTRAINT_TYPE_RIGIDBODYJOINT); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_CLAMPTO.constraintId), CONSTRAINT_TYPE_CLAMPTO); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_TRANSFORM.constraintId), CONSTRAINT_TYPE_TRANSFORM); |
||||||
|
idsMap.put(Integer.valueOf(CONSTRAINT_TYPE_SHRINKWRAP.constraintId), CONSTRAINT_TYPE_SHRINKWRAP); |
||||||
|
} |
||||||
|
/** |
||||||
|
* Constructor. Stores constraint type and class name. |
||||||
|
* @param constraintId |
||||||
|
* the constraint's type |
||||||
|
* @param className |
||||||
|
* the constraint's type name |
||||||
|
*/ |
||||||
|
private ConstraintType(int constraintId, String className) { |
||||||
|
this.constraintId = constraintId; |
||||||
|
this.className = className; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the type by given constraint id. |
||||||
|
* @param constraintId |
||||||
|
* the id of the constraint |
||||||
|
* @return the constraint type enum value |
||||||
|
*/ |
||||||
|
public static ConstraintType valueOf(int constraintId) { |
||||||
|
return idsMap.get(Integer.valueOf(constraintId)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the constraint's id (type). |
||||||
|
* @return the constraint's id (type) |
||||||
|
*/ |
||||||
|
public int getConstraintId() { |
||||||
|
return constraintId; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the constraint's class name. |
||||||
|
* @return the constraint's class name |
||||||
|
*/ |
||||||
|
public String getClassName() { |
||||||
|
return className; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns constraint enum type by the given class name. |
||||||
|
* @param className |
||||||
|
* the blender's constraint class name |
||||||
|
* @return the constraint enum type of the specified class name |
||||||
|
*/ |
||||||
|
public static ConstraintType getByBlenderClassName(String className) { |
||||||
|
ConstraintType result = typesMap.get(className); |
||||||
|
if(result == null) { |
||||||
|
ConstraintType[] constraints = ConstraintType.values(); |
||||||
|
for(ConstraintType constraint : constraints) { |
||||||
|
if(constraint.className.equals(className)) { |
||||||
|
return constraint; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the type value of the last defined constraint. It can be used for allocating tables for |
||||||
|
* storing constraint procedures since not all type values from 0 to the last value are used. |
||||||
|
* @return the type value of the last defined constraint |
||||||
|
*/ |
||||||
|
public static int getLastDefinedTypeValue() { |
||||||
|
return CONSTRAINT_TYPE_SHRINKWRAP.getConstraintId(); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,102 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.utils; |
||||||
|
|
||||||
|
import java.nio.FloatBuffer; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import com.jme3.util.BufferUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can |
||||||
|
* hold the state of the calculations. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public abstract class AbstractBlenderHelper { |
||||||
|
/** The version of the blend file. */ |
||||||
|
protected final int blenderVersion; |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender |
||||||
|
* versions. |
||||||
|
* @param blenderVersion |
||||||
|
* the version read from the blend file |
||||||
|
*/ |
||||||
|
public AbstractBlenderHelper(String blenderVersion) { |
||||||
|
this.blenderVersion = Integer.parseInt(blenderVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method clears the state of the helper so that it can be used for different calculations of another feature. |
||||||
|
*/ |
||||||
|
public void clearState() { } |
||||||
|
|
||||||
|
/** |
||||||
|
* This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are |
||||||
|
* being created and stored in the memory. It can be unwise especially inside loops. |
||||||
|
* @param text |
||||||
|
* the text to be checked |
||||||
|
* @return <b>true</b> if the text is blank and <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
protected boolean isBlank(String text) { |
||||||
|
if (text != null) { |
||||||
|
for (int i = 0; i < text.length(); ++i) { |
||||||
|
if (!Character.isWhitespace(text.charAt(i))) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generate a new FloatBuffer using the given array of float[4] objects. The FloatBuffer will be 4 * data.length |
||||||
|
* long and contain the vector data as data[0][0], data[0][1], data[0][2], data[0][3], data[1][0]... etc. |
||||||
|
* @param data |
||||||
|
* list of float[4] objects to place into a new FloatBuffer |
||||||
|
*/ |
||||||
|
protected FloatBuffer createFloatBuffer(List<float[]> data) { |
||||||
|
if(data == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
FloatBuffer buff = BufferUtils.createFloatBuffer(4 * data.size()); |
||||||
|
for(float[] v : data) { |
||||||
|
if(v != null) { |
||||||
|
buff.put(v[0]).put(v[1]).put(v[2]).put(v[3]); |
||||||
|
} else { |
||||||
|
buff.put(0).put(0).put(0).put(0); |
||||||
|
} |
||||||
|
} |
||||||
|
buff.flip(); |
||||||
|
return buff; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,382 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.utils; |
||||||
|
|
||||||
|
import java.io.BufferedInputStream; |
||||||
|
import java.io.ByteArrayInputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.util.logging.Logger; |
||||||
|
import java.util.zip.GZIPInputStream; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
|
||||||
|
/** |
||||||
|
* An input stream with random access to data. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class BlenderInputStream extends InputStream { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName()); |
||||||
|
|
||||||
|
/** The default size of the blender buffer. */ |
||||||
|
private static final int DEFAULT_BUFFER_SIZE = 1048576; //1MB
|
||||||
|
/** The application's asset manager. */ |
||||||
|
private AssetManager assetManager; |
||||||
|
/** |
||||||
|
* Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes. |
||||||
|
*/ |
||||||
|
private int pointerSize; |
||||||
|
/** |
||||||
|
* Type of byte ordering used; 'v' means little endian and 'V' means big endian. |
||||||
|
*/ |
||||||
|
private char endianess; |
||||||
|
/** Version of Blender the file was created in; '248' means version 2.48. */ |
||||||
|
private String versionNumber; |
||||||
|
/** The buffer we store the read data to. */ |
||||||
|
protected byte[] cachedBuffer; |
||||||
|
/** The total size of the stored data. */ |
||||||
|
protected int size; |
||||||
|
/** The current position of the read cursor. */ |
||||||
|
protected int position; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. The input stream is stored and used to read data. |
||||||
|
* @param inputStream |
||||||
|
* the stream we read data from |
||||||
|
* @param assetManager |
||||||
|
* the application's asset manager |
||||||
|
* @param endianess |
||||||
|
* type of byte ordering used; 'v' means little endian and 'V' means big endian |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown if the file header has some invalid data |
||||||
|
*/ |
||||||
|
public BlenderInputStream(InputStream inputStream, AssetManager assetManager) throws BlenderFileException { |
||||||
|
this.assetManager = assetManager; |
||||||
|
//the size value will canche while reading the file; the available() method cannot be counted on
|
||||||
|
try { |
||||||
|
size = inputStream.available(); |
||||||
|
} catch (IOException e) { |
||||||
|
size = 0; |
||||||
|
} |
||||||
|
if(size <= 0) { |
||||||
|
size = BlenderInputStream.DEFAULT_BUFFER_SIZE; |
||||||
|
} |
||||||
|
|
||||||
|
//buffered input stream is used here for much faster file reading
|
||||||
|
BufferedInputStream bufferedInputStream; |
||||||
|
if(inputStream instanceof BufferedInputStream) { |
||||||
|
bufferedInputStream = (BufferedInputStream)inputStream; |
||||||
|
} else { |
||||||
|
bufferedInputStream = new BufferedInputStream(inputStream); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
this.readStreamToCache(bufferedInputStream); |
||||||
|
} catch (IOException e) { |
||||||
|
throw new BlenderFileException("Problems occured while caching the file!", e); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
this.readFileHeader(); |
||||||
|
} catch(BlenderFileException e) {//the file might be packed, don't panic, try one more time ;)
|
||||||
|
this.decompressFile(); |
||||||
|
this.position = 0; |
||||||
|
this.readFileHeader(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads the whole stream into a buffer. |
||||||
|
* @param inputStream |
||||||
|
* the stream to read the file data from |
||||||
|
* @throws IOException |
||||||
|
* an exception is thrown when data read from the stream is invalid or there are problems with i/o |
||||||
|
* operations |
||||||
|
*/ |
||||||
|
private void readStreamToCache(InputStream inputStream) throws IOException { |
||||||
|
int data = inputStream.read(); |
||||||
|
cachedBuffer = new byte[size]; |
||||||
|
size = 0;//this will count the actual size
|
||||||
|
while(data != -1) { |
||||||
|
cachedBuffer[size++] = (byte)data; |
||||||
|
if(size >= cachedBuffer.length) {//widen the cached array
|
||||||
|
byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)]; |
||||||
|
System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length); |
||||||
|
cachedBuffer = newBuffer; |
||||||
|
} |
||||||
|
data = inputStream.read(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method is used when the blender file is gzipped. It decompresses the data and stores it back into the |
||||||
|
* cachedBuffer field. |
||||||
|
*/ |
||||||
|
private void decompressFile() { |
||||||
|
GZIPInputStream gis = null; |
||||||
|
try { |
||||||
|
gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer)); |
||||||
|
this.readStreamToCache(gis); |
||||||
|
} catch (IOException e) { |
||||||
|
throw new IllegalStateException("IO errors occured where they should NOT! " + |
||||||
|
"The data is already buffered at this point!", e); |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
if(gis!=null) { |
||||||
|
gis.close(); |
||||||
|
} |
||||||
|
} catch(IOException e) { |
||||||
|
LOGGER.warning(e.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method loads the header from the given stream during instance creation. |
||||||
|
* @param inputStream |
||||||
|
* the stream we read the header from |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown if the file header has some invalid data |
||||||
|
*/ |
||||||
|
private void readFileHeader() throws BlenderFileException { |
||||||
|
byte[] identifier = new byte[7]; |
||||||
|
int bytesRead = this.readBytes(identifier); |
||||||
|
if(bytesRead != 7) { |
||||||
|
throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!"); |
||||||
|
} |
||||||
|
String strIdentifier = new String(identifier); |
||||||
|
if(!"BLENDER".equals(strIdentifier)) { |
||||||
|
throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!"); |
||||||
|
} |
||||||
|
char pointerSizeSign = (char)this.readByte(); |
||||||
|
if(pointerSizeSign == '-') { |
||||||
|
pointerSize = 8; |
||||||
|
} else if(pointerSizeSign == '_') { |
||||||
|
pointerSize = 4; |
||||||
|
} else { |
||||||
|
throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign); |
||||||
|
} |
||||||
|
endianess = (char)this.readByte(); |
||||||
|
if(endianess != 'v' && endianess != 'V') { |
||||||
|
throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess); |
||||||
|
} |
||||||
|
byte[] versionNumber = new byte[3]; |
||||||
|
bytesRead = this.readBytes(versionNumber); |
||||||
|
if(bytesRead != 3) { |
||||||
|
throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!"); |
||||||
|
} |
||||||
|
this.versionNumber = new String(versionNumber); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int read() throws IOException { |
||||||
|
return this.readByte(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads 1 byte from the stream. |
||||||
|
* It works just in the way the read method does. |
||||||
|
* It just not throw an exception because at this moment the whole file |
||||||
|
* is loaded into buffer, so no need for IOException to be thrown. |
||||||
|
* @return a byte from the stream (1 bytes read) |
||||||
|
*/ |
||||||
|
public int readByte() { |
||||||
|
return cachedBuffer[position++] & 0xFF; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads a bytes number big enough to fill the table. |
||||||
|
* It does not throw exceptions so it is for internal use only. |
||||||
|
* @param bytes |
||||||
|
* an array to be filled with data |
||||||
|
* @return number of read bytes (a length of array actually) |
||||||
|
*/ |
||||||
|
private int readBytes(byte[] bytes) { |
||||||
|
for(int i=0;i<bytes.length;++i) { |
||||||
|
bytes[i] = (byte) this.readByte(); |
||||||
|
} |
||||||
|
return bytes.length; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads 2-byte number from the stream. |
||||||
|
* @return a number from the stream (2 bytes read) |
||||||
|
*/ |
||||||
|
public int readShort() { |
||||||
|
int part1 = this.readByte(); |
||||||
|
int part2 = this.readByte(); |
||||||
|
if(endianess == 'v') { |
||||||
|
return (part2 << 8) + part1; |
||||||
|
} else { |
||||||
|
return (part1 << 8) + part2; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads 4-byte number from the stream. |
||||||
|
* @return a number from the stream (4 bytes read) |
||||||
|
*/ |
||||||
|
public int readInt() { |
||||||
|
int part1 = this.readByte(); |
||||||
|
int part2 = this.readByte(); |
||||||
|
int part3 = this.readByte(); |
||||||
|
int part4 = this.readByte(); |
||||||
|
if(endianess == 'v') { |
||||||
|
return (part4 << 24) + (part3 << 16) + (part2 << 8) + part1; |
||||||
|
} else { |
||||||
|
return (part1 << 24) + (part2 << 16) + (part3 << 8) + part4; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads 4-byte floating point number (float) from the stream. |
||||||
|
* @return a number from the stream (4 bytes read) |
||||||
|
*/ |
||||||
|
public float readFloat() { |
||||||
|
int intValue = this.readInt(); |
||||||
|
return Float.intBitsToFloat(intValue); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads 8-byte number from the stream. |
||||||
|
* @return a number from the stream (8 bytes read) |
||||||
|
*/ |
||||||
|
public long readLong() { |
||||||
|
long part1 = this.readInt(); |
||||||
|
long part2 = this.readInt(); |
||||||
|
long result = -1; |
||||||
|
if(endianess == 'v') { |
||||||
|
result = part2 << 32 | part1; |
||||||
|
} else { |
||||||
|
result = part1 << 32 | part2; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads 8-byte floating point number (double) from the stream. |
||||||
|
* @return a number from the stream (8 bytes read) |
||||||
|
*/ |
||||||
|
public double readDouble() { |
||||||
|
long longValue = this.readLong(); |
||||||
|
return Double.longBitsToDouble(longValue); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads the pointer value. Depending on the pointer size defined in the header, the stream reads either |
||||||
|
* 4 or 8 bytes of data. |
||||||
|
* @return the pointer value |
||||||
|
*/ |
||||||
|
public long readPointer() { |
||||||
|
if(pointerSize == 4) { |
||||||
|
return this.readInt(); |
||||||
|
} |
||||||
|
return this.readLong(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads the string. It assumes the string is terminated with zero in the stream. |
||||||
|
* @return the string read from the stream |
||||||
|
*/ |
||||||
|
public String readString() { |
||||||
|
StringBuilder stringBuilder = new StringBuilder(); |
||||||
|
int data = this.readByte(); |
||||||
|
while(data != 0) { |
||||||
|
stringBuilder.append((char)data); |
||||||
|
data = this.readByte(); |
||||||
|
} |
||||||
|
return stringBuilder.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the current position of the read cursor. |
||||||
|
* @param position |
||||||
|
* the position of the read cursor |
||||||
|
*/ |
||||||
|
public void setPosition(int position) { |
||||||
|
this.position = position; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the position of the read cursor. |
||||||
|
* @return the position of the read cursor |
||||||
|
*/ |
||||||
|
public int getPosition() { |
||||||
|
return position; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the blender version number where the file was created. |
||||||
|
* @return blender version number |
||||||
|
*/ |
||||||
|
public String getVersionNumber() { |
||||||
|
return versionNumber; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the size of the pointer. |
||||||
|
* @return the size of the pointer |
||||||
|
*/ |
||||||
|
public int getPointerSize() { |
||||||
|
return pointerSize; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the application's asset manager. |
||||||
|
* @return the application's asset manager |
||||||
|
*/ |
||||||
|
public AssetManager getAssetManager() { |
||||||
|
return assetManager; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method aligns cursor position forward to a given amount of bytes. |
||||||
|
* @param bytesAmount |
||||||
|
* the byte amount to which we aligh the cursor |
||||||
|
*/ |
||||||
|
public void alignPosition(int bytesAmount) { |
||||||
|
if(bytesAmount <= 0) { |
||||||
|
throw new IllegalArgumentException("Alignment byte number shoulf be positivbe!"); |
||||||
|
} |
||||||
|
long move = position % bytesAmount; |
||||||
|
if(move > 0) { |
||||||
|
position += bytesAmount - move; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() throws IOException { |
||||||
|
// cachedBuffer = null;
|
||||||
|
// size = position = 0;
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,400 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.utils; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.EmptyStackException; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Stack; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.asset.BlenderKey; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.scene.plugins.blender.data.DnaBlockData; |
||||||
|
import com.jme3.scene.plugins.blender.data.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Ipo; |
||||||
|
import com.jme3.scene.plugins.blender.structures.Modifier; |
||||||
|
|
||||||
|
/** |
||||||
|
* The class that stores temporary data and manages it during loading the belnd file. This class is intended to be used |
||||||
|
* in a single loading thread. It holds the state of loading operations. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class DataRepository { |
||||||
|
/** The blender key. */ |
||||||
|
private BlenderKey blenderKey; |
||||||
|
/** The header of the file block. */ |
||||||
|
private DnaBlockData dnaBlockData; |
||||||
|
/** The input stream of the blend file. */ |
||||||
|
private BlenderInputStream inputStream; |
||||||
|
/** The asset manager. */ |
||||||
|
private AssetManager assetManager; |
||||||
|
/** A map containing the file block headers. The key is the old pointer address. */ |
||||||
|
private Map<Long, FileBlockHeader> fileBlockHeadersByOma = new HashMap<Long, FileBlockHeader>(); |
||||||
|
/** A map containing the file block headers. The key is the block code. */ |
||||||
|
private Map<Integer, List<FileBlockHeader>> fileBlockHeadersByCode = new HashMap<Integer, List<FileBlockHeader>>(); |
||||||
|
/** |
||||||
|
* This map stores the loaded features by their old memory address. The first object in the value table is the |
||||||
|
* loaded structure and the second - the structure already converted into proper data. |
||||||
|
*/ |
||||||
|
private Map<Long, Object[]> loadedFeatures = new HashMap<Long, Object[]>(); |
||||||
|
/** |
||||||
|
* This map stores the loaded features by their name. Only features with ID structure can be stored here. |
||||||
|
* The first object in the value table is the |
||||||
|
* loaded structure and the second - the structure already converted into proper data. |
||||||
|
*/ |
||||||
|
private Map<String, Object[]> loadedFeaturesByName = new HashMap<String, Object[]>(); |
||||||
|
/** A stack that hold the parent structure of currently loaded feature. */ |
||||||
|
private Stack<Structure> parentStack = new Stack<Structure>(); |
||||||
|
/** A map storing loaded ipos. The key is the ipo's owner old memory address and the value is the ipo. */ |
||||||
|
private Map<Long, Ipo> loadedIpos = new HashMap<Long, Ipo>(); |
||||||
|
/** A list of modifiers for the specified object. */ |
||||||
|
protected Map<Long, List<Modifier>> modifiers = new HashMap<Long, List<Modifier>>(); |
||||||
|
/** A map og helpers that perform loading. */ |
||||||
|
private Map<String, AbstractBlenderHelper> helpers = new HashMap<String, AbstractBlenderHelper>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the blender key. |
||||||
|
* @param blenderKey |
||||||
|
* the blender key |
||||||
|
*/ |
||||||
|
public void setBlenderKey(BlenderKey blenderKey) { |
||||||
|
this.blenderKey = blenderKey; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the blender key. |
||||||
|
* @return the blender key |
||||||
|
*/ |
||||||
|
public BlenderKey getBlenderKey() { |
||||||
|
return blenderKey; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the dna block data. |
||||||
|
* @param dnaBlockData |
||||||
|
* the dna block data |
||||||
|
*/ |
||||||
|
public void setBlockData(DnaBlockData dnaBlockData) { |
||||||
|
this.dnaBlockData = dnaBlockData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the dna block data. |
||||||
|
* @return the dna block data |
||||||
|
*/ |
||||||
|
public DnaBlockData getDnaBlockData() { |
||||||
|
return dnaBlockData; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the asset manager. |
||||||
|
* @return the asset manager |
||||||
|
*/ |
||||||
|
public AssetManager getAssetManager() { |
||||||
|
return assetManager; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the asset manager. |
||||||
|
* @param assetManager |
||||||
|
* the asset manager |
||||||
|
*/ |
||||||
|
public void setAssetManager(AssetManager assetManager) { |
||||||
|
this.assetManager = assetManager; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the input stream of the blend file. |
||||||
|
* @return the input stream of the blend file |
||||||
|
*/ |
||||||
|
public BlenderInputStream getInputStream() { |
||||||
|
return inputStream; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method sets the input stream of the blend file. |
||||||
|
* @param inputStream |
||||||
|
* the input stream of the blend file |
||||||
|
*/ |
||||||
|
public void setInputStream(BlenderInputStream inputStream) { |
||||||
|
this.inputStream = inputStream; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a file block header to the map. Its old memory address is the key. |
||||||
|
* @param oldMemoryAddress |
||||||
|
* the address of the block header |
||||||
|
* @param fileBlockHeader |
||||||
|
* the block header to store |
||||||
|
*/ |
||||||
|
public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { |
||||||
|
fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); |
||||||
|
List<FileBlockHeader> headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode())); |
||||||
|
if(headers == null) { |
||||||
|
headers = new ArrayList<FileBlockHeader>(); |
||||||
|
fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers); |
||||||
|
} |
||||||
|
headers.add(fileBlockHeader); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the block header of a given memory address. If the header is not present then null is |
||||||
|
* returned. |
||||||
|
* @param oldMemoryAddress |
||||||
|
* the address of the block header |
||||||
|
* @return loaded header or null if it was not yet loaded |
||||||
|
*/ |
||||||
|
public FileBlockHeader getFileBlock(Long oldMemoryAddress) { |
||||||
|
return fileBlockHeadersByOma.get(oldMemoryAddress); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a list of file blocks' headers of a specified code. |
||||||
|
* @param code |
||||||
|
* the code of file blocks |
||||||
|
* @return a list of file blocks' headers of a specified code |
||||||
|
*/ |
||||||
|
public List<FileBlockHeader> getFileBlocks(Integer code) { |
||||||
|
return fileBlockHeadersByCode.get(code); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method clears the saved block headers stored in the features map. |
||||||
|
*/ |
||||||
|
public void clearFileBlocks() { |
||||||
|
fileBlockHeadersByOma.clear(); |
||||||
|
fileBlockHeadersByCode.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a helper instance to the helpers' map. |
||||||
|
* @param <T> |
||||||
|
* the type of the helper |
||||||
|
* @param clazz |
||||||
|
* helper's class definition |
||||||
|
* @param helper |
||||||
|
* the helper instance |
||||||
|
*/ |
||||||
|
public <T> void putHelper(Class<T> clazz, AbstractBlenderHelper helper) { |
||||||
|
helpers.put(clazz.getSimpleName(), helper); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public <T> T getHelper(Class<?> clazz) { |
||||||
|
return (T)helpers.get(clazz.getSimpleName()); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a loaded feature to the map. The key is its unique old memory address. |
||||||
|
* @param oldMemoryAddress |
||||||
|
* the address of the feature |
||||||
|
* @param featureName the name of the feature |
||||||
|
* @param structure |
||||||
|
* the filled structure of the feature |
||||||
|
* @param feature |
||||||
|
* the feature we want to store |
||||||
|
*/ |
||||||
|
public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) { |
||||||
|
if(oldMemoryAddress == null || structure == null || feature == null) { |
||||||
|
throw new IllegalArgumentException("One of the given arguments is null!"); |
||||||
|
} |
||||||
|
Object[] storedData = new Object[] {structure, feature}; |
||||||
|
loadedFeatures.put(oldMemoryAddress, storedData); |
||||||
|
if(featureName!=null) { |
||||||
|
loadedFeaturesByName.put(featureName, storedData); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the feature of a given memory address. If the feature is not yet loaded then null is |
||||||
|
* returned. |
||||||
|
* @param oldMemoryAddress |
||||||
|
* the address of the feature |
||||||
|
* @param loadedFeatureDataType |
||||||
|
* the type of data we want to retreive it can be either filled structure or already converted feature |
||||||
|
* @return loaded feature or null if it was not yet loaded |
||||||
|
*/ |
||||||
|
public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) { |
||||||
|
Object[] result = loadedFeatures.get(oldMemoryAddress); |
||||||
|
if(result != null) { |
||||||
|
return result[loadedFeatureDataType.getIndex()]; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the feature of a given name. If the feature is not yet loaded then null is |
||||||
|
* returned. |
||||||
|
* @param featureName |
||||||
|
* the name of the feature |
||||||
|
* @param loadedFeatureDataType |
||||||
|
* the type of data we want to retreive it can be either filled structure or already converted feature |
||||||
|
* @return loaded feature or null if it was not yet loaded |
||||||
|
*/ |
||||||
|
public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) { |
||||||
|
Object[] result = loadedFeaturesByName.get(featureName); |
||||||
|
if(result != null) { |
||||||
|
return result[loadedFeatureDataType.getIndex()]; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method clears the saved features stored in the features map. |
||||||
|
*/ |
||||||
|
public void clearLoadedFeatures() { |
||||||
|
loadedFeatures.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds the structure to the parent stack. |
||||||
|
* @param parent |
||||||
|
* the structure to be added to the stack |
||||||
|
*/ |
||||||
|
public void pushParent(Structure parent) { |
||||||
|
parentStack.push(parent); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method removes the structure from the top of the parent's stack. |
||||||
|
* @return the structure that was removed from the stack |
||||||
|
*/ |
||||||
|
public Structure popParent() { |
||||||
|
try { |
||||||
|
return parentStack.pop(); |
||||||
|
} catch(EmptyStackException e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method retreives the structure at the top of the parent's stack but does not remove it. |
||||||
|
* @return the structure from the top of the stack |
||||||
|
*/ |
||||||
|
public Structure peekParent() { |
||||||
|
try { |
||||||
|
return parentStack.peek(); |
||||||
|
} catch(EmptyStackException e) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void addIpo(Long ownerOMA, Ipo ipo) { |
||||||
|
loadedIpos.put(ownerOMA, ipo); |
||||||
|
} |
||||||
|
|
||||||
|
public Ipo removeIpo(Long ownerOma) { |
||||||
|
return loadedIpos.remove(ownerOma); |
||||||
|
} |
||||||
|
|
||||||
|
public Ipo getIpo(Long ownerOMA) { |
||||||
|
return loadedIpos.get(ownerOMA); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method adds a new modifier to the list. |
||||||
|
* @param ownerOMA |
||||||
|
* the owner's old memory address |
||||||
|
* @param modifierType |
||||||
|
* the type of the modifier |
||||||
|
* @param loadedModifier |
||||||
|
* the loaded modifier object |
||||||
|
*/ |
||||||
|
public void addModifier(Long ownerOMA, String modifierType, Object loadedModifier, Object additionalModifierData) { |
||||||
|
List<Modifier> objectModifiers = this.modifiers.get(ownerOMA); |
||||||
|
if(objectModifiers == null) { |
||||||
|
objectModifiers = new ArrayList<Modifier>(); |
||||||
|
this.modifiers.put(ownerOMA, objectModifiers); |
||||||
|
} |
||||||
|
objectModifiers.add(new Modifier(modifierType, loadedModifier, additionalModifierData)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns modifiers for the object specified by its old memory address and the modifier type. If no |
||||||
|
* modifiers are found - empty list is returned. If the type is null - all modifiers for the object are returned. |
||||||
|
* @param objectOMA |
||||||
|
* object's old memory address |
||||||
|
* @param type |
||||||
|
* the type of the modifier |
||||||
|
* @return the list of object's modifiers |
||||||
|
*/ |
||||||
|
public List<Modifier> getModifiers(Long objectOMA, String type) { |
||||||
|
List<Modifier> result = new ArrayList<Modifier>(); |
||||||
|
List<Modifier> readModifiers = modifiers.get(objectOMA); |
||||||
|
if(readModifiers != null && readModifiers.size() > 0) { |
||||||
|
for(Modifier modifier : readModifiers) { |
||||||
|
if(type==null || type.isEmpty() || modifier.getType().equals(type)) { |
||||||
|
result.add(modifier); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This metod returns the default material. |
||||||
|
* @return the default material |
||||||
|
*/ |
||||||
|
public synchronized Material getDefaultMaterial() { |
||||||
|
if(blenderKey.getDefaultMaterial() == null) { |
||||||
|
Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md"); |
||||||
|
defaultMaterial.setColor("Color", ColorRGBA.DarkGray); |
||||||
|
blenderKey.setDefaultMaterial(defaultMaterial); |
||||||
|
} |
||||||
|
return blenderKey.getDefaultMaterial(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This enum defines what loaded data type user wants to retreive. It can be either filled structure or already |
||||||
|
* converted data. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public static enum LoadedFeatureDataType { |
||||||
|
LOADED_STRUCTURE(0), LOADED_FEATURE(1); |
||||||
|
|
||||||
|
private int index; |
||||||
|
|
||||||
|
private LoadedFeatureDataType(int index) { |
||||||
|
this.index = index; |
||||||
|
} |
||||||
|
|
||||||
|
public int getIndex() { |
||||||
|
return index; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,156 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.utils; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
|
||||||
|
/** |
||||||
|
* An array that can be dynamically modified/ |
||||||
|
* @author Marcin Roguski |
||||||
|
* @param <T> |
||||||
|
* the type of stored data in the array |
||||||
|
*/ |
||||||
|
public class DynamicArray<T> implements Cloneable { |
||||||
|
/** An array object that holds the required data. */ |
||||||
|
private T[] array; |
||||||
|
/** |
||||||
|
* This table holds the sizes of dimetions of the dynamic table. It's length specifies the table dimension or a |
||||||
|
* pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths: |
||||||
|
* dynTable[a][b][c], where a,b,c are stored in the tableSizes table. |
||||||
|
*/ |
||||||
|
private int[] tableSizes; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Builds an empty array of the specified sizes. |
||||||
|
* @param tableSizes |
||||||
|
* the sizes of the table |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown if one of the sizes is not a positive number |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public DynamicArray(int[] tableSizes) throws BlenderFileException { |
||||||
|
this.tableSizes = tableSizes; |
||||||
|
int totalSize = 1; |
||||||
|
for(int size : tableSizes) { |
||||||
|
if(size <= 0) { |
||||||
|
throw new BlenderFileException("The size of the table must be positive!"); |
||||||
|
} |
||||||
|
totalSize *= size; |
||||||
|
} |
||||||
|
this.array = (T[])new Object[totalSize]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Builds an empty array of the specified sizes. |
||||||
|
* @param tableSizes |
||||||
|
* the sizes of the table |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown if one of the sizes is not a positive number |
||||||
|
*/ |
||||||
|
public DynamicArray(int[] tableSizes, T[] data) throws BlenderFileException { |
||||||
|
this.tableSizes = tableSizes; |
||||||
|
int totalSize = 1; |
||||||
|
for(int size : tableSizes) { |
||||||
|
if(size <= 0) { |
||||||
|
throw new BlenderFileException("The size of the table must be positive!"); |
||||||
|
} |
||||||
|
totalSize *= size; |
||||||
|
} |
||||||
|
if(totalSize != data.length) { |
||||||
|
throw new IllegalArgumentException("The size of the table does not match the size of the given data!"); |
||||||
|
} |
||||||
|
this.array = data; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object clone() throws CloneNotSupportedException { |
||||||
|
return super.clone(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a value on the specified position. The dimension of the table is not taken into |
||||||
|
* consideration. |
||||||
|
* @param position |
||||||
|
* the position of the data |
||||||
|
* @return required data |
||||||
|
*/ |
||||||
|
public T get(int position) { |
||||||
|
return array[position]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns a value on the specified position in multidimensional array. Be careful not to exceed the |
||||||
|
* table boundaries. Check the table's dimension first. |
||||||
|
* @param position |
||||||
|
* the position of the data indices of data position |
||||||
|
* @return required data required data |
||||||
|
*/ |
||||||
|
public T get(int... position) { |
||||||
|
if(position.length != tableSizes.length) { |
||||||
|
throw new ArrayIndexOutOfBoundsException("The table accepts " + tableSizes.length + " indexing number(s)!"); |
||||||
|
} |
||||||
|
int index = 0; |
||||||
|
for(int i = 0; i < position.length - 1; ++i) { |
||||||
|
index += position[i] * tableSizes[i + 1]; |
||||||
|
} |
||||||
|
index += position[position.length - 1]; |
||||||
|
return array[index]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the total amount of data stored in the array. |
||||||
|
* @return the total amount of data stored in the array |
||||||
|
*/ |
||||||
|
public int getTotalSize() { |
||||||
|
return array.length; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
StringBuilder result = new StringBuilder(); |
||||||
|
if(array instanceof Character[]) {//in case of character array we convert it to String
|
||||||
|
for(int i = 0; i < array.length && (Character)array[i] != '\0'; ++i) {//strings are terminater with '0'
|
||||||
|
result.append(array[i]); |
||||||
|
} |
||||||
|
} else { |
||||||
|
result.append('['); |
||||||
|
for(int i = 0; i < array.length; ++i) { |
||||||
|
result.append(array[i].toString()); |
||||||
|
if(i + 1 < array.length) { |
||||||
|
result.append(','); |
||||||
|
} |
||||||
|
} |
||||||
|
result.append(']'); |
||||||
|
} |
||||||
|
return result.toString(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,109 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.utils; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
|
||||||
|
/** |
||||||
|
* This interface provides an abstraction to converting loaded blender structures into data structures. The data |
||||||
|
* structures can vary and therefore one can use the loader for different kind of engines. |
||||||
|
* @author Marcin Roguski |
||||||
|
* @param <NodeType> |
||||||
|
* the type of the scene node element |
||||||
|
* @param <CameraType> |
||||||
|
* the type of camera element |
||||||
|
* @param <LightType> |
||||||
|
* the type of light element |
||||||
|
* @param <ObjectType> |
||||||
|
* the type of object element |
||||||
|
* @param <MeshType> |
||||||
|
* the type of mesh element |
||||||
|
* @param <MaterialType> |
||||||
|
* the type of material element |
||||||
|
*/ |
||||||
|
//TODO: ujednolicić wyrzucane wyjątki
|
||||||
|
public interface IBlenderConverter<NodeType, CameraType, LightType, ObjectType, MeshType, MaterialType> { |
||||||
|
/** |
||||||
|
* This method reads converts the given structure into scene. The given structure needs to be filled with the |
||||||
|
* appropriate data. |
||||||
|
* @param structure |
||||||
|
* the structure we read the scene from |
||||||
|
* @return the scene feature |
||||||
|
*/ |
||||||
|
NodeType toScene(Structure structure); |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads converts the given structure into camera. The given structure needs to be filled with the |
||||||
|
* appropriate data. |
||||||
|
* @param structure |
||||||
|
* the structure we read the camera from |
||||||
|
* @return the camera feature |
||||||
|
*/ |
||||||
|
CameraType toCamera(Structure structure) throws BlenderFileException; |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads converts the given structure into light. The given structure needs to be filled with the |
||||||
|
* appropriate data. |
||||||
|
* @param structure |
||||||
|
* the structure we read the light from |
||||||
|
* @return the light feature |
||||||
|
*/ |
||||||
|
LightType toLight(Structure structure) throws BlenderFileException; |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads converts the given structure into objct. The given structure needs to be filled with the |
||||||
|
* appropriate data. |
||||||
|
* @param structure |
||||||
|
* the structure we read the object from |
||||||
|
* @return the object feature |
||||||
|
*/ |
||||||
|
ObjectType toObject(Structure structure) throws BlenderFileException; |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads converts the given structure into mesh. The given structure needs to be filled with the |
||||||
|
* appropriate data. |
||||||
|
* @param structure |
||||||
|
* the structure we read the mesh from |
||||||
|
* @return the mesh feature |
||||||
|
*/ |
||||||
|
MeshType toMesh(Structure structure) throws BlenderFileException; |
||||||
|
|
||||||
|
/** |
||||||
|
* This method reads converts the given structure into material. The given structure needs to be filled with the |
||||||
|
* appropriate data. |
||||||
|
* @param structure |
||||||
|
* the structure we read the material from |
||||||
|
* @return the material feature |
||||||
|
*/ |
||||||
|
MaterialType toMaterial(Structure structure) throws BlenderFileException; |
||||||
|
} |
@ -0,0 +1,161 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.utils; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.asset.BlenderKey.FeaturesToLoad; |
||||||
|
import com.jme3.asset.BlenderKey.WorldData; |
||||||
|
import com.jme3.light.AmbientLight; |
||||||
|
import com.jme3.light.Light; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.renderer.Camera; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.CameraHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.LightHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.MaterialHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.MeshHelper; |
||||||
|
import com.jme3.scene.plugins.blender.helpers.ObjectHelper; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class converts blender file blocks into jMonkeyEngine data structures. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class JmeConverter implements IBlenderConverter<Node, Camera, Light, Object, List<Geometry>, Material> { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(JmeConverter.class.getName()); |
||||||
|
|
||||||
|
private final DataRepository dataRepository; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Creates the loader and checks if the given data is correct. |
||||||
|
* @param dataRepository |
||||||
|
* the data repository; it should have the following field set: - asset manager - blender key - dna block |
||||||
|
* data - blender input stream Otherwise IllegalArgumentException will be thrown. |
||||||
|
* @param featuresToLoad |
||||||
|
* bitwise flag describing what features are to be loaded |
||||||
|
* @see FeaturesToLoad FeaturesToLoad |
||||||
|
*/ |
||||||
|
public JmeConverter(DataRepository dataRepository) { |
||||||
|
//validating the given data first
|
||||||
|
if(dataRepository.getAssetManager() == null) { |
||||||
|
throw new IllegalArgumentException("Cannot find asset manager!"); |
||||||
|
} |
||||||
|
if(dataRepository.getBlenderKey() == null) { |
||||||
|
throw new IllegalArgumentException("Cannot find blender key!"); |
||||||
|
} |
||||||
|
if(dataRepository.getDnaBlockData() == null) { |
||||||
|
throw new IllegalArgumentException("Cannot find dna block!"); |
||||||
|
} |
||||||
|
if(dataRepository.getInputStream() == null) { |
||||||
|
throw new IllegalArgumentException("Cannot find blender file stream!"); |
||||||
|
} |
||||||
|
this.dataRepository = dataRepository; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Node toScene(Structure structure) {//TODO: poprawny import sceny
|
||||||
|
if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
Structure id = (Structure)structure.getFieldValue("id"); |
||||||
|
String sceneName = id.getFieldValue("name").toString(); |
||||||
|
return new Node(sceneName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Camera toCamera(Structure structure) throws BlenderFileException { |
||||||
|
if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) == 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
CameraHelper cameraHelper = dataRepository.getHelper(CameraHelper.class); |
||||||
|
return cameraHelper.toCamera(structure); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Light toLight(Structure structure) throws BlenderFileException { |
||||||
|
if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) == 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
LightHelper lightHelper = dataRepository.getHelper(LightHelper.class); |
||||||
|
return lightHelper.toLight(structure, dataRepository); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object toObject(Structure structure) throws BlenderFileException { |
||||||
|
if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) == 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
ObjectHelper objectHelper = dataRepository.getHelper(ObjectHelper.class); |
||||||
|
return objectHelper.toObject(structure, dataRepository); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<Geometry> toMesh(Structure structure) throws BlenderFileException { |
||||||
|
MeshHelper meshHelper = dataRepository.getHelper(MeshHelper.class); |
||||||
|
return meshHelper.toMesh(structure, dataRepository); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Material toMaterial(Structure structure) throws BlenderFileException { |
||||||
|
if((dataRepository.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) == 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
MaterialHelper materialHelper = dataRepository.getHelper(MaterialHelper.class); |
||||||
|
return materialHelper.toMaterial(structure, dataRepository); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the data read from the WORLD file block. The block contains data that can be stored as |
||||||
|
* separate jme features and therefore cannot be returned as a single jME scene feature. |
||||||
|
* @param structure |
||||||
|
* the structure with WORLD block data |
||||||
|
* @return data read from the WORLD block that can be added to the scene |
||||||
|
*/ |
||||||
|
public WorldData toWorldData(Structure structure) { |
||||||
|
WorldData result = new WorldData(); |
||||||
|
|
||||||
|
//reading ambient light
|
||||||
|
AmbientLight ambientLight = new AmbientLight(); |
||||||
|
float ambr = ((Number)structure.getFieldValue("ambr")).floatValue(); |
||||||
|
float ambg = ((Number)structure.getFieldValue("ambg")).floatValue(); |
||||||
|
float ambb = ((Number)structure.getFieldValue("ambb")).floatValue(); |
||||||
|
ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f)); |
||||||
|
result.setAmbientLight(ambientLight); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,175 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2010 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.blender.utils; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.blender.data.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.data.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.exception.BlenderFileException; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that represents a pointer of any level that can be stored in the file. |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class Pointer { |
||||||
|
/** The data repository. */ |
||||||
|
private DataRepository dataRepository; |
||||||
|
/** The level of the pointer. */ |
||||||
|
private int pointerLevel; |
||||||
|
/** The address in file it points to. */ |
||||||
|
private long oldMemoryAddress; |
||||||
|
/** This variable indicates if the field is a function pointer. */ |
||||||
|
public boolean function; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructr. Stores the basic data about the pointer. |
||||||
|
* @param pointerLevel |
||||||
|
* the level of the pointer |
||||||
|
* @param function |
||||||
|
* this variable indicates if the field is a function pointer |
||||||
|
* @param dataRepository |
||||||
|
* the repository f data; used in fetching the value that the pointer points |
||||||
|
*/ |
||||||
|
public Pointer(int pointerLevel, boolean function, DataRepository dataRepository) { |
||||||
|
this.pointerLevel = pointerLevel; |
||||||
|
this.function = function; |
||||||
|
this.dataRepository = dataRepository; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method |
||||||
|
* for this. |
||||||
|
* @param inputStream |
||||||
|
* the stream we read the pointer value from |
||||||
|
*/ |
||||||
|
public void fill(BlenderInputStream inputStream) { |
||||||
|
oldMemoryAddress = inputStream.readPointer(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method fetches the data stored under the given address. |
||||||
|
* @param inputStream |
||||||
|
* the stream we read data from |
||||||
|
* @param dataIndices |
||||||
|
* the offset of the data in the table pointed by the pointer |
||||||
|
* @return the data read from the file |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blend file structure is somehow invalid or corrupted |
||||||
|
*/ |
||||||
|
public List<Structure> fetchData(BlenderInputStream inputStream) throws BlenderFileException { |
||||||
|
if(oldMemoryAddress == 0) { |
||||||
|
throw new NullPointerException("The pointer points to nothing!"); |
||||||
|
} |
||||||
|
List<Structure> structures = null; |
||||||
|
FileBlockHeader dataFileBlock = dataRepository.getFileBlock(oldMemoryAddress); |
||||||
|
if(pointerLevel > 1) { |
||||||
|
int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount(); |
||||||
|
for(int i = 0; i < pointersAmount; ++i) { |
||||||
|
inputStream.setPosition(dataFileBlock.getBlockPosition() + inputStream.getPointerSize() * i); |
||||||
|
long oldMemoryAddress = inputStream.readPointer(); |
||||||
|
if(oldMemoryAddress != 0L) { |
||||||
|
Pointer p = new Pointer(pointerLevel - 1, this.function, dataRepository); |
||||||
|
p.oldMemoryAddress = oldMemoryAddress; |
||||||
|
if(structures == null) { |
||||||
|
structures = p.fetchData(inputStream); |
||||||
|
} else { |
||||||
|
structures.addAll(p.fetchData(inputStream)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
inputStream.setPosition(dataFileBlock.getBlockPosition()); |
||||||
|
structures = new ArrayList<Structure>(dataFileBlock.getCount()); |
||||||
|
for(int i = 0; i < dataFileBlock.getCount(); ++i) { |
||||||
|
Structure structure = dataRepository.getDnaBlockData().getStructure(dataFileBlock.getSdnaIndex()); |
||||||
|
structure.fill(inputStream); |
||||||
|
structures.add(structure); |
||||||
|
} |
||||||
|
return structures; |
||||||
|
} |
||||||
|
return structures; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method indicates if this pointer points to a function. |
||||||
|
* @return <b>true</b> if this is a function pointer and <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
public boolean isFunction() { |
||||||
|
return function; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method indicates if this is a null-pointer or not. |
||||||
|
* @return <b>true</b> if the pointer is null and <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
public boolean isNull() { |
||||||
|
return oldMemoryAddress == 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the old memory address of the structure pointed by the pointer. |
||||||
|
* @return the old memory address of the structure pointed by the pointer |
||||||
|
*/ |
||||||
|
public long getOldMemoryAddress() { |
||||||
|
return oldMemoryAddress; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return oldMemoryAddress == 0 ? "{$null$}" : "{$" + oldMemoryAddress + "$}"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return 31 + (int)(oldMemoryAddress ^ oldMemoryAddress >>> 32); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if(this == obj) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if(obj == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if(this.getClass() != obj.getClass()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
Pointer other = (Pointer)obj; |
||||||
|
if(oldMemoryAddress != other.oldMemoryAddress) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue