diff --git a/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg b/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg
index 18c82f5bc..01bc5045e 100644
--- a/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg
+++ b/jme3-core/src/main/resources/com/jme3/asset/Desktop.cfg
@@ -22,3 +22,4 @@ LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material
LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene
LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib
+LOADER com.jme3.scene.plugins.fbx.SceneLoader : fbx
diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle
index f4857a60f..dabbe62a0 100644
--- a/jme3-plugins/build.gradle
+++ b/jme3-plugins/build.gradle
@@ -6,6 +6,7 @@ sourceSets {
main {
java {
srcDir 'src/ogre/java'
+ srcDir 'src/fbx/java'
srcDir 'src/xml/java'
}
}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java
new file mode 100644
index 000000000..6ca884b8c
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2014 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.fbx;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines animations set that will be created while loading FBX scene
+ *
Animation name is using to access animation via {@link AnimControl}.
+ * firstFrame and lastFrame defines animation time interval.
+ * Use layerName also to define source animation layer in the case of multiple layers in the scene.
+ * Skeletal animations will be created if only scene contain skeletal bones
+ */
+public class AnimationList {
+
+ List list = new ArrayList();
+
+ /**
+ * Use in the case of multiple animation layers in FBX asset
+ * @param name - animation name to assess via {@link AnimControl}
+ * @param layerName - source layer
+ */
+ public void add(String name, int firstFrame, int lastFrame) {
+ add(name, null, firstFrame, lastFrame);
+ }
+
+ /**
+ * Use in the case of multiple animation layers in FBX asset
+ * @param name - animation name to assess via {@link AnimControl}
+ * @param layerName - source layer
+ */
+ public void add(String name, String layerName, int firstFrame, int lastFrame) {
+ AnimInverval cue = new AnimInverval();
+ cue.name = name;
+ cue.layerName = layerName;
+ cue.firstFrame = firstFrame;
+ cue.lastFrame = lastFrame;
+ list.add(cue);
+ }
+
+ static class AnimInverval {
+ String name;
+ String layerName;
+ int firstFrame;
+ int lastFrame;
+ }
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java
new file mode 100644
index 000000000..06a4ec795
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2014 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.fbx;
+
+import com.jme3.asset.TextureKey;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+
+import java.io.IOException;
+
+/**
+ * Used to load textures from image binary content.
+ *
Filename is required to acquire proper type asset loader according to extension.
+ */
+public class ContentTextureKey extends TextureKey {
+
+ private byte[] content;
+
+ public ContentTextureKey(String name, byte[] content) {
+ super(name);
+ this.content = content;
+ }
+
+ public byte[] getContent() {
+ return content;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " " + content.length + " bytes";
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(content, "content", new byte[0]);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ content = ic.readByteArray("content", new byte[0]);
+ }
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java
new file mode 100644
index 000000000..76902159c
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2014 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.fbx;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetLocator;
+import com.jme3.asset.AssetManager;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Used to locate a resource based on a {@link ContentTextureKey}.
+ */
+public class ContentTextureLocator implements AssetLocator {
+
+ private static final Logger logger = Logger.getLogger(ContentTextureLocator.class.getName());
+
+ @Override
+ public void setRootPath(String rootPath) {
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public AssetInfo locate(AssetManager manager, AssetKey key) {
+ if(key instanceof ContentTextureKey) {
+ String name = key.getName();
+ byte[] content = ((ContentTextureKey) key).getContent();
+ if(content != null) {
+ return new ContentAssetInfo(manager, key, content);
+ } else {
+ logger.log(Level.WARNING, "No content for " + name);
+ return null;
+ }
+ } else {
+ logger.log(Level.SEVERE, "AssetKey should be TextureContentKey instance");
+ return null;
+ }
+ }
+
+ private class ContentAssetInfo extends AssetInfo {
+
+ private InputStream stream;
+
+ @SuppressWarnings("rawtypes")
+ public ContentAssetInfo(AssetManager assetManager, AssetKey key, byte[] content) {
+ super(assetManager, key);
+ this.stream = (content != null) ? new ByteArrayInputStream(content) : null;
+ }
+
+ @Override
+ public InputStream openStream() {
+ return stream;
+ }
+ }
+
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java
new file mode 100644
index 000000000..a4362d46d
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2014 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.fbx;
+
+import com.jme3.asset.ModelKey;
+
+public class SceneKey extends ModelKey {
+
+ private final AnimationList animList;
+
+ public SceneKey(String name) {
+ super(name);
+ this.animList = null;
+ }
+
+ public SceneKey(String name, AnimationList animationList) {
+ super(name);
+ this.animList = animationList;
+ }
+
+ public AnimationList getAnimations() {
+ return this.animList;
+ }
+
+}
\ No newline at end of file
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
new file mode 100644
index 000000000..545d4cc92
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
@@ -0,0 +1,1586 @@
+/*
+ * Copyright (c) 2009-2014 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.fbx;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+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.AnimControl;
+import com.jme3.animation.Animation;
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.animation.SkeletonControl;
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetLoadException;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+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.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.plugins.fbx.AnimationList.AnimInverval;
+import com.jme3.scene.plugins.fbx.file.FBXElement;
+import com.jme3.scene.plugins.fbx.file.FBXFile;
+import com.jme3.scene.plugins.fbx.file.FBXReader;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.BufferUtils;
+
+/**
+ * FBX file format loader
+ *
Loads scene meshes, materials, textures, skeleton and skeletal animation.
+ * Multiple animations can be defined with {@link AnimationList} passing into {@link SceneKey}.
+ *
+ * @author Aleksandra Menshchikova
+ */
+public class SceneLoader implements AssetLoader {
+
+ private static final Logger logger = Logger.getLogger(SceneLoader.class.getName());
+
+ private AssetManager assetManager;
+ private SceneKey key;
+
+ private String sceneName;
+ private String sceneFilename;
+ private String sceneFolderName;
+ private float unitSize;
+ private float animFrameRate;
+ private final double secondsPerUnit = 1 / 46186158000d; // Animation speed factor
+
+ // Loaded objects data
+ private Map meshDataMap = new HashMap();
+ private Map matDataMap = new HashMap();
+ private Map texDataMap = new HashMap();
+ private Map imgDataMap = new HashMap(); // Video clips
+ private Map modelDataMap = new HashMap(); // Mesh nodes and limb nodes
+ private Map poseDataMap = new HashMap(); // Node bind poses
+ private Map skinMap = new HashMap(); // Skin for bone clusters
+ private Map clusterMap = new HashMap(); // Bone skinning cluster
+ private Map acurveMap = new HashMap(); // Animation curves
+ private Map anodeMap = new HashMap(); // Animation nodes
+ private Map alayerMap = new HashMap(); // Amination layers
+ private Map> refMap = new HashMap>(); // Object links
+ private Map> propMap = new HashMap>(); // Property links
+
+ // Scene objects
+ private Map modelMap = new HashMap(); // Mesh nodes
+ private Map limbMap = new HashMap(); // Bones
+ private Map bindMap = new HashMap(); // Node bind poses
+ private Map geomMap = new HashMap(); // Mesh geometries
+ private Map matMap = new HashMap();
+ private Map texMap = new HashMap();
+ private Map imgMap = new HashMap();
+ private Skeleton skeleton;
+ private AnimControl animControl;
+
+ @Override
+ public Object load(AssetInfo assetInfo) throws IOException {
+ this.assetManager = assetInfo.getManager();
+ AssetKey> assetKey = assetInfo.getKey();
+ if(assetKey instanceof SceneKey)
+ key = (SceneKey) assetKey;
+ else
+ throw new AssetLoadException("Invalid asset key");
+ InputStream stream = assetInfo.openStream();
+ Node sceneNode = null;
+ try {
+ sceneFilename = assetKey.getName();
+ sceneFolderName = assetKey.getFolder();
+ String ext = assetKey.getExtension();
+ sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1);
+ if(sceneFolderName != null && sceneFolderName.length() > 0)
+ sceneName = sceneName.substring(sceneFolderName.length());
+ reset();
+ loadScene(stream);
+ sceneNode = linkScene();
+ } finally {
+ releaseObjects();
+ if(stream != null) {
+ stream.close();
+ }
+ }
+ return sceneNode;
+ }
+
+ private void reset() {
+ unitSize = 1;
+ animFrameRate = 30;
+ }
+
+ private void loadScene(InputStream stream) throws IOException {
+ logger.log(Level.FINE, "Loading scene {0}", sceneFilename);
+ long startTime = System.currentTimeMillis();
+ FBXFile scene = FBXReader.readFBX(stream);
+ for(FBXElement e : scene.rootElements) {
+ if(e.id.equals("GlobalSettings"))
+ loadGlobalSettings(e);
+ else if(e.id.equals("Objects"))
+ loadObjects(e);
+ else if(e.id.equals("Connections"))
+ loadConnections(e);
+ }
+ long estimatedTime = System.currentTimeMillis() - startTime;
+ logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime);
+ }
+
+ private void loadGlobalSettings(FBXElement element) {
+ for(FBXElement e : element.children) {
+ if(e.id.equals("Properties70")) {
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("P")) {
+ String propName = (String) e2.properties.get(0);
+ if(propName.equals("UnitScaleFactor"))
+ this.unitSize = ((Double) e2.properties.get(4)).floatValue();
+ else if(propName.equals("CustomFrameRate")) {
+ float framerate = ((Double) e2.properties.get(4)).floatValue();
+ if(framerate != -1)
+ this.animFrameRate = framerate;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void loadObjects(FBXElement element) {
+ for(FBXElement e : element.children) {
+ if(e.id.equals("Geometry"))
+ loadGeometry(e);
+ else if(e.id.equals("Material"))
+ loadMaterial(e);
+ else if(e.id.equals("Model"))
+ loadModel(e);
+ else if(e.id.equals("Pose"))
+ loadPose(e);
+ else if(e.id.equals("Texture"))
+ loadTexture(e);
+ else if(e.id.equals("Video"))
+ loadImage(e);
+ else if(e.id.equals("Deformer"))
+ loadDeformer(e);
+ else if(e.id.equals("AnimationLayer"))
+ loadAnimLayer(e);
+ else if(e.id.equals("AnimationCurve"))
+ loadAnimCurve(e);
+ else if(e.id.equals("AnimationCurveNode"))
+ loadAnimNode(e);
+ }
+ }
+
+ private void loadGeometry(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String type = (String) element.properties.get(2);
+ if(type.equals("Mesh")) {
+ MeshData data = new MeshData();
+ for(FBXElement e : element.children) {
+ if(e.id.equals("Vertices"))
+ data.vertices = (double[]) e.properties.get(0);
+ else if(e.id.equals("PolygonVertexIndex"))
+ data.indices = (int[]) e.properties.get(0);
+ // TODO edges are not used now
+ //else if(e.id.equals("Edges"))
+ // data.edges = (int[]) e.properties.get(0);
+ else if(e.id.equals("LayerElementNormal"))
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("MappingInformationType")) {
+ data.normalsMapping = (String) e2.properties.get(0);
+ if(!data.normalsMapping.equals("ByVertice") && !data.normalsMapping.equals("ByPolygonVertex"))
+ throw new AssetLoadException("Not supported LayerElementNormal.MappingInformationType = " + data.normalsMapping);
+ } else if(e2.id.equals("ReferenceInformationType")) {
+ data.normalsReference = (String) e2.properties.get(0);
+ if(!data.normalsReference.equals("Direct"))
+ throw new AssetLoadException("Not supported LayerElementNormal.ReferenceInformationType = " + data.normalsReference);
+ } else if(e2.id.equals("Normals"))
+ data.normals = (double[]) e2.properties.get(0);
+ }
+ else if(e.id.equals("LayerElementTangent"))
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("MappingInformationType")) {
+ data.tangentsMapping = (String) e2.properties.get(0);
+ if(!data.tangentsMapping.equals("ByVertice") && !data.tangentsMapping.equals("ByPolygonVertex"))
+ throw new AssetLoadException("Not supported LayerElementTangent.MappingInformationType = " + data.tangentsMapping);
+ } else if(e2.id.equals("ReferenceInformationType")) {
+ data.tangentsReference = (String) e2.properties.get(0);
+ if(!data.tangentsReference.equals("Direct"))
+ throw new AssetLoadException("Not supported LayerElementTangent.ReferenceInformationType = " + data.tangentsReference);
+ } else if(e2.id.equals("Tangents"))
+ data.tangents = (double[]) e2.properties.get(0);
+ }
+ else if(e.id.equals("LayerElementBinormal"))
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("MappingInformationType")) {
+ data.binormalsMapping = (String) e2.properties.get(0);
+ if(!data.binormalsMapping.equals("ByVertice") && !data.binormalsMapping.equals("ByPolygonVertex"))
+ throw new AssetLoadException("Not supported LayerElementBinormal.MappingInformationType = " + data.binormalsMapping);
+ } else if(e2.id.equals("ReferenceInformationType")) {
+ data.binormalsReference = (String) e2.properties.get(0);
+ if(!data.binormalsReference.equals("Direct"))
+ throw new AssetLoadException("Not supported LayerElementBinormal.ReferenceInformationType = " + data.binormalsReference);
+ } else if(e2.id.equals("Tangents"))
+ data.binormals = (double[]) e2.properties.get(0);
+ }
+ else if(e.id.equals("LayerElementUV"))
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("MappingInformationType")) {
+ data.uvMapping = (String) e2.properties.get(0);
+ if(!data.uvMapping.equals("ByPolygonVertex"))
+ throw new AssetLoadException("Not supported LayerElementUV.MappingInformationType = " + data.uvMapping);
+ } else if(e2.id.equals("ReferenceInformationType")) {
+ data.uvReference = (String) e2.properties.get(0);
+ if(!data.uvReference.equals("IndexToDirect"))
+ throw new AssetLoadException("Not supported LayerElementUV.ReferenceInformationType = " + data.uvReference);
+ } else if(e2.id.equals("UV"))
+ data.uv = (double[]) e2.properties.get(0);
+ else if(e2.id.equals("UVIndex"))
+ data.uvIndex = (int[]) e2.properties.get(0);
+ }
+ // TODO smoothing is not used now
+ //else if(e.id.equals("LayerElementSmoothing"))
+ // for(FBXElement e2 : e.children) {
+ // if(e2.id.equals("MappingInformationType")) {
+ // data.smoothingMapping = (String) e2.properties.get(0);
+ // if(!data.smoothingMapping.equals("ByEdge"))
+ // throw new AssetLoadException("Not supported LayerElementSmoothing.MappingInformationType = " + data.smoothingMapping);
+ // } else if(e2.id.equals("ReferenceInformationType")) {
+ // data.smoothingReference = (String) e2.properties.get(0);
+ // if(!data.smoothingReference.equals("Direct"))
+ // throw new AssetLoadException("Not supported LayerElementSmoothing.ReferenceInformationType = " + data.smoothingReference);
+ // } else if(e2.id.equals("Smoothing"))
+ // data.smoothing = (int[]) e2.properties.get(0);
+ // }
+ else if(e.id.equals("LayerElementMaterial"))
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("MappingInformationType")) {
+ data.materialsMapping = (String) e2.properties.get(0);
+ if(!data.materialsMapping.equals("AllSame"))
+ throw new AssetLoadException("Not supported LayerElementMaterial.MappingInformationType = " + data.materialsMapping);
+ } else if(e2.id.equals("ReferenceInformationType")) {
+ data.materialsReference = (String) e2.properties.get(0);
+ if(!data.materialsReference.equals("IndexToDirect"))
+ throw new AssetLoadException("Not supported LayerElementMaterial.ReferenceInformationType = " + data.materialsReference);
+ } else if(e2.id.equals("Materials"))
+ data.materials = (int[]) e2.properties.get(0);
+ }
+ }
+ meshDataMap.put(id, data);
+ }
+ }
+
+ private void loadMaterial(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String path = (String) element.properties.get(1);
+ String type = (String) element.properties.get(2);
+ if(type.equals("")) {
+ MaterialData data = new MaterialData();
+ data.name = path.substring(0, path.indexOf(0));
+ for(FBXElement e : element.children) {
+ if(e.id.equals("ShadingModel")) {
+ data.shadingModel = (String) e.properties.get(0);
+ } else if(e.id.equals("Properties70")) {
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("P")) {
+ String propName = (String) e2.properties.get(0);
+ if(propName.equals("AmbientColor")) {
+ double x = (Double) e2.properties.get(4);
+ double y = (Double) e2.properties.get(5);
+ double z = (Double) e2.properties.get(6);
+ data.ambientColor.set((float) x, (float) y, (float) z);
+ } else if(propName.equals("AmbientFactor")) {
+ double x = (Double) e2.properties.get(4);
+ data.ambientFactor = (float) x;
+ } else if(propName.equals("DiffuseColor")) {
+ double x = (Double) e2.properties.get(4);
+ double y = (Double) e2.properties.get(5);
+ double z = (Double) e2.properties.get(6);
+ data.diffuseColor.set((float) x, (float) y, (float) z);
+ } else if(propName.equals("DiffuseFactor")) {
+ double x = (Double) e2.properties.get(4);
+ data.diffuseFactor = (float) x;
+ } else if(propName.equals("SpecularColor")) {
+ double x = (Double) e2.properties.get(4);
+ double y = (Double) e2.properties.get(5);
+ double z = (Double) e2.properties.get(6);
+ data.specularColor.set((float) x, (float) y, (float) z);
+ } else if(propName.equals("Shininess") || propName.equals("ShininessExponent")) {
+ double x = (Double) e2.properties.get(4);
+ data.shininessExponent = (float) x;
+ }
+ }
+ }
+ }
+ }
+ matDataMap.put(id, data);
+ }
+ }
+
+ private void loadModel(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String path = (String) element.properties.get(1);
+ String type = (String) element.properties.get(2);
+ ModelData data = new ModelData();
+ data.name = path.substring(0, path.indexOf(0));
+ data.type = type;
+ for(FBXElement e : element.children) {
+ if(e.id.equals("Properties70")) {
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("P")) {
+ String propName = (String) e2.properties.get(0);
+ if(propName.equals("Lcl Translation")) {
+ double x = (Double) e2.properties.get(4);
+ double y = (Double) e2.properties.get(5);
+ double z = (Double) e2.properties.get(6);
+ data.localTranslation.set((float) x, (float) y, (float) z).divideLocal(unitSize);
+ } else if(propName.equals("Lcl Rotation")) {
+ double x = (Double) e2.properties.get(4);
+ double y = (Double) e2.properties.get(5);
+ double z = (Double) e2.properties.get(6);
+ data.localRotation.fromAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD);
+ } else if(propName.equals("Lcl Scaling")) {
+ double x = (Double) e2.properties.get(4);
+ double y = (Double) e2.properties.get(5);
+ double z = (Double) e2.properties.get(6);
+ data.localScale.set((float) x, (float) y, (float) z).multLocal(unitSize);
+ } else if(propName.equals("PreRotation")) {
+ double x = (Double) e2.properties.get(4);
+ double y = (Double) e2.properties.get(5);
+ double z = (Double) e2.properties.get(6);
+ data.preRotation = quatFromBoneAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD);
+ }
+ }
+ }
+ }
+ }
+ modelDataMap.put(id, data);
+ }
+
+ private void loadPose(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String path = (String) element.properties.get(1);
+ String type = (String) element.properties.get(2);
+ if(type.equals("BindPose")) {
+ BindPoseData data = new BindPoseData();
+ data.name = path.substring(0, path.indexOf(0));
+ for(FBXElement e : element.children) {
+ if(e.id.equals("PoseNode")) {
+ NodeTransformData item = new NodeTransformData();
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("Node"))
+ item.nodeId = (Long) e2.properties.get(0);
+ else if(e2.id.equals("Matrix"))
+ item.transform = (double[]) e2.properties.get(0);
+ }
+ data.list.add(item);
+ }
+ }
+ poseDataMap.put(id, data);
+ }
+ }
+
+ private void loadTexture(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String path = (String) element.properties.get(1);
+ String type = (String) element.properties.get(2);
+ if(type.equals("")) {
+ TextureData data = new TextureData();
+ data.name = path.substring(0, path.indexOf(0));
+ for(FBXElement e : element.children) {
+ if(e.id.equals("Type"))
+ data.bindType = (String) e.properties.get(0);
+ else if(e.id.equals("FileName"))
+ data.filename = (String) e.properties.get(0);
+ }
+ texDataMap.put(id, data);
+ }
+ }
+
+ private void loadImage(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String path = (String) element.properties.get(1);
+ String type = (String) element.properties.get(2);
+ if(type.equals("Clip")) {
+ ImageData data = new ImageData();
+ data.name = path.substring(0, path.indexOf(0));
+ for(FBXElement e : element.children) {
+ if(e.id.equals("Type"))
+ data.type = (String) e.properties.get(0);
+ else if(e.id.equals("Filename"))
+ data.filename = (String) e.properties.get(0);
+ else if(e.id.equals("RelativeFilename"))
+ data.relativeFilename = (String) e.properties.get(0);
+ else if(e.id.equals("Content")) {
+ if(e.properties.size() > 0)
+ data.content = (byte[]) e.properties.get(0);
+ }
+ }
+ imgDataMap.put(id, data);
+ }
+ }
+
+ private void loadDeformer(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String type = (String) element.properties.get(2);
+ if(type.equals("Skin")) {
+ SkinData skinData = new SkinData();
+ for(FBXElement e : element.children) {
+ if(e.id.equals("SkinningType"))
+ skinData.type = (String) e.properties.get(0);
+ }
+ skinMap.put(id, skinData);
+ } else if(type.equals("Cluster")) {
+ ClusterData clusterData = new ClusterData();
+ for(FBXElement e : element.children) {
+ if(e.id.equals("Indexes"))
+ clusterData.indexes = (int[]) e.properties.get(0);
+ else if(e.id.equals("Weights"))
+ clusterData.weights = (double[]) e.properties.get(0);
+ else if(e.id.equals("Transform"))
+ clusterData.transform = (double[]) e.properties.get(0);
+ else if(e.id.equals("TransformLink"))
+ clusterData.transformLink = (double[]) e.properties.get(0);
+ }
+ clusterMap.put(id, clusterData);
+ }
+ }
+
+ private void loadAnimLayer(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String path = (String) element.properties.get(1);
+ String type = (String) element.properties.get(2);
+ if(type.equals("")) {
+ AnimLayer layer = new AnimLayer();
+ layer.name = path.substring(0, path.indexOf(0));
+ alayerMap.put(id, layer);
+ }
+ }
+
+ private void loadAnimCurve(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String type = (String) element.properties.get(2);
+ if(type.equals("")) {
+ AnimCurveData data = new AnimCurveData();
+ for(FBXElement e : element.children) {
+ if(e.id.equals("KeyTime"))
+ data.keyTimes = (long[]) e.properties.get(0);
+ else if(e.id.equals("KeyValueFloat"))
+ data.keyValues = (float[]) e.properties.get(0);
+ }
+ acurveMap.put(id, data);
+ }
+ }
+
+ private void loadAnimNode(FBXElement element) {
+ long id = (Long) element.properties.get(0);
+ String path = (String) element.properties.get(1);
+ String type = (String) element.properties.get(2);
+ if(type.equals("")) {
+ Double x = null, y = null, z = null;
+ for(FBXElement e : element.children) {
+ if(e.id.equals("Properties70")) {
+ for(FBXElement e2 : e.children) {
+ if(e2.id.equals("P")) {
+ String propName = (String) e2.properties.get(0);
+ if(propName.equals("d|X"))
+ x = (Double) e2.properties.get(4);
+ else if(propName.equals("d|Y"))
+ y = (Double) e2.properties.get(4);
+ else if(propName.equals("d|Z"))
+ z = (Double) e2.properties.get(4);
+ }
+ }
+ }
+ }
+ // Load only T R S curve nodes
+ if(x != null && y != null && z != null) {
+ AnimNode node = new AnimNode();
+ node.value = new Vector3f(x.floatValue(), y.floatValue(), z.floatValue());
+ node.name = path.substring(0, path.indexOf(0));
+ anodeMap.put(id, node);
+ }
+ }
+ }
+
+ private void loadConnections(FBXElement element) {
+ for(FBXElement e : element.children) {
+ if(e.id.equals("C")) {
+ String type = (String) e.properties.get(0);
+ long objId, refId;
+ if(type.equals("OO")) {
+ objId = (Long) e.properties.get(1);
+ refId = (Long) e.properties.get(2);
+ List links = refMap.get(objId);
+ if(links == null) {
+ links = new ArrayList();
+ refMap.put(objId, links);
+ }
+ links.add(refId);
+ } else if(type.equals("OP")) {
+ objId = (Long) e.properties.get(1);
+ refId = (Long) e.properties.get(2);
+ String propName = (String) e.properties.get(3);
+ List props = propMap.get(objId);
+ if(props == null) {
+ props = new ArrayList();
+ propMap.put(objId, props);
+ }
+ props.add(new PropertyLink(refId, propName));
+ }
+ }
+ }
+ }
+
+ private Geometry createGeomerty(MeshData data) {
+ Mesh mesh = new Mesh();
+ mesh.setMode(Mode.Triangles);
+ // Since each vertex should contain unique texcoord and normal we should unroll vertex indexing
+ // So we don't use VertexBuffer.Type.Index for elements drawing
+ // Moreover quads should be triangulated (this increases number of vertices)
+ boolean isQuads = false;
+ if(data.indices != null) {
+ data.iCount = data.indices.length;
+ data.srcVertexCount = data.vertices.length / 3;
+ // Indices contains negative numbers to define polygon last index
+ // Check indices strides to be sure we have triangles or quads
+ boolean allTriangles = true;
+ boolean allQads = true;
+ for(int i = 0; i < data.indices.length; ++i) {
+ if(i % 3 == 2) { // Triangle stride
+ if(data.indices[i] >= 0)
+ allTriangles = false;
+ } else {
+ if(data.indices[i] < 0)
+ allTriangles = false;
+ }
+ if(i % 4 == 3) { // Quad stride
+ if(data.indices[i] >= 0)
+ allQads = false;
+ } else {
+ if(data.indices[i] < 0)
+ allQads = false;
+ }
+ }
+ if(allTriangles) {
+ isQuads = false;
+ data.vCount = data.iCount;
+ } else if(allQads) {
+ isQuads = true;
+ data.vCount = 6 * (data.iCount / 4); // Each quad will be splited into two triangles
+ } else
+ throw new AssetLoadException("Unsupported PolygonVertexIndex stride");
+ data.vertexMap = new int[data.vCount];
+ data.indexMap = new int[data.vCount];
+ // Unroll index array into vertex mapping
+ int n = 0;
+ for(int i = 0; i < data.iCount; ++i) {
+ int index = data.indices[i];
+ if(index < 0) {
+ int lastIndex = -(index + 1);
+ if(isQuads) {
+ data.vertexMap[n + 0] = data.indices[i - 3];
+ data.vertexMap[n + 1] = data.indices[i - 2];
+ data.vertexMap[n + 2] = data.indices[i - 1];
+ data.vertexMap[n + 3] = data.indices[i - 3];
+ data.vertexMap[n + 4] = data.indices[i - 1];
+ data.vertexMap[n + 5] = lastIndex;
+ data.indexMap[n + 0] = (i - 3);
+ data.indexMap[n + 1] = (i - 2);
+ data.indexMap[n + 2] = (i - 1);
+ data.indexMap[n + 3] = (i - 3);
+ data.indexMap[n + 4] = (i - 1);
+ data.indexMap[n + 5] = (i - 0);
+ n += 6;
+ } else {
+ data.vertexMap[n + 0] = data.indices[i - 2];
+ data.vertexMap[n + 1] = data.indices[i - 1];
+ data.vertexMap[n + 2] = lastIndex;
+ data.indexMap[n + 0] = (i - 2);
+ data.indexMap[n + 1] = (i - 1);
+ data.indexMap[n + 2] = (i - 0);
+ n += 3;
+ }
+ }
+ }
+ // Build reverse vertex mapping
+ data.reverseVertexMap = new ArrayList>(data.srcVertexCount);
+ for(int i = 0; i < data.srcVertexCount; ++i)
+ data.reverseVertexMap.add(new ArrayList());
+ for(int i = 0; i < data.vCount; ++i) {
+ int index = data.vertexMap[i];
+ data.reverseVertexMap.get(index).add(i);
+ }
+ } else {
+ // Stub for no vertex indexing (direct mapping)
+ data.iCount = data.vCount = data.srcVertexCount;
+ data.vertexMap = new int[data.vCount];
+ data.indexMap = new int[data.vCount];
+ data.reverseVertexMap = new ArrayList>(data.vCount);
+ for(int i = 0; i < data.vCount; ++i) {
+ data.vertexMap[i] = i;
+ data.indexMap[i] = i;
+ List reverseIndices = new ArrayList(1);
+ reverseIndices.add(i);
+ data.reverseVertexMap.add(reverseIndices);
+ }
+ }
+ if(data.vertices != null) {
+ // Unroll vertices data array
+ FloatBuffer posBuf = BufferUtils.createFloatBuffer(data.vCount * 3);
+ mesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
+ int srcCount = data.vertices.length / 3;
+ for(int i = 0; i < data.vCount; ++i) {
+ int index = data.vertexMap[i];
+ if(index > srcCount)
+ throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount);
+ float x = (float) data.vertices[3 * index + 0] / unitSize;
+ float y = (float) data.vertices[3 * index + 1] / unitSize;
+ float z = (float) data.vertices[3 * index + 2] / unitSize;
+ posBuf.put(x).put(y).put(z);
+ }
+ }
+ if(data.normals != null) {
+ // Unroll normals data array
+ FloatBuffer normBuf = BufferUtils.createFloatBuffer(data.vCount * 3);
+ mesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf);
+ int[] mapping = null;
+ if(data.normalsMapping.equals("ByVertice"))
+ mapping = data.vertexMap;
+ else if(data.normalsMapping.equals("ByPolygonVertex"))
+ mapping = data.indexMap;
+ int srcCount = data.normals.length / 3;
+ for(int i = 0; i < data.vCount; ++i) {
+ int index = mapping[i];
+ if(index > srcCount)
+ throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount);
+ float x = (float) data.normals[3 * index + 0];
+ float y = (float) data.normals[3 * index + 1];
+ float z = (float) data.normals[3 * index + 2];
+ normBuf.put(x).put(y).put(z);
+ }
+ }
+ if(data.tangents != null) {
+ // Unroll normals data array
+ FloatBuffer tanBuf = BufferUtils.createFloatBuffer(data.vCount * 4);
+ mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tanBuf);
+ int[] mapping = null;
+ if(data.tangentsMapping.equals("ByVertice"))
+ mapping = data.vertexMap;
+ else if(data.tangentsMapping.equals("ByPolygonVertex"))
+ mapping = data.indexMap;
+ int srcCount = data.tangents.length / 3;
+ for(int i = 0; i < data.vCount; ++i) {
+ int index = mapping[i];
+ if(index > srcCount)
+ throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount);
+ float x = (float) data.tangents[3 * index + 0];
+ float y = (float) data.tangents[3 * index + 1];
+ float z = (float) data.tangents[3 * index + 2];
+ tanBuf.put(x).put(y).put(z).put(-1.0f);
+ }
+ }
+ if(data.binormals != null) {
+ // Unroll normals data array
+ FloatBuffer binormBuf = BufferUtils.createFloatBuffer(data.vCount * 3);
+ mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormBuf);
+ int[] mapping = null;
+ if(data.binormalsMapping.equals("ByVertice"))
+ mapping = data.vertexMap;
+ else if(data.binormalsMapping.equals("ByPolygonVertex"))
+ mapping = data.indexMap;
+ int srcCount = data.binormals.length / 3;
+ for(int i = 0; i < data.vCount; ++i) {
+ int index = mapping[i];
+ if(index > srcCount)
+ throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount);
+ float x = (float) data.binormals[3 * index + 0];
+ float y = (float) data.binormals[3 * index + 1];
+ float z = (float) data.binormals[3 * index + 2];
+ binormBuf.put(x).put(y).put(z);
+ }
+ }
+ if(data.uv != null) {
+ int[] unIndexMap = data.vertexMap;
+ if(data.uvIndex != null) {
+ int uvIndexSrcCount = data.uvIndex.length;
+ if(uvIndexSrcCount != data.iCount)
+ throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + data.iCount);
+ // Unroll UV index array
+ unIndexMap = new int[data.vCount];
+ int n = 0;
+ for(int i = 0; i < data.iCount; ++i) {
+ int index = data.uvIndex[i];
+ if(isQuads && (i % 4) == 3) {
+ unIndexMap[n + 0] = data.uvIndex[i - 3];
+ unIndexMap[n + 1] = data.uvIndex[i - 1];
+ unIndexMap[n + 2] = index;
+ n += 3;
+ } else {
+ unIndexMap[i] = index;
+ }
+ }
+ }
+ // Unroll UV data array
+ FloatBuffer tcBuf = BufferUtils.createFloatBuffer(data.vCount * 2);
+ mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, tcBuf);
+ int srcCount = data.uv.length / 2;
+ for(int i = 0; i < data.vCount; ++i) {
+ int index = unIndexMap[i];
+ if(index > srcCount)
+ throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount);
+ float u = (float) data.uv[2 * index + 0];
+ float v = (float) data.uv[2 * index + 1];
+ tcBuf.put(u).put(v);
+ }
+ }
+ mesh.setStatic();
+ mesh.updateBound();
+ mesh.updateCounts();
+ Geometry geom = new Geometry();
+ geom.setMesh(mesh);
+ return geom;
+ }
+
+ private Material createMaterial(MaterialData data) {
+ Material m = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+ m.setName(data.name);
+ data.ambientColor.multLocal(data.ambientFactor);
+ data.diffuseColor.multLocal(data.diffuseFactor);
+ data.specularColor.multLocal(data.specularFactor);
+ m.setColor("Ambient", new ColorRGBA(data.ambientColor.x, data.ambientColor.y, data.ambientColor.z, 1));
+ m.setColor("Diffuse", new ColorRGBA(data.diffuseColor.x, data.diffuseColor.y, data.diffuseColor.z, 1));
+ m.setColor("Specular", new ColorRGBA(data.specularColor.x, data.specularColor.y, data.specularColor.z, 1));
+ m.setFloat("Shininess", data.shininessExponent);
+ m.setBoolean("UseMaterialColors", true);
+ m.getAdditionalRenderState().setAlphaTest(true);
+ m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+ return m;
+ }
+
+ private Node createNode(ModelData data) {
+ Node model = new Node(data.name);
+ model.setLocalTranslation(data.localTranslation);
+ model.setLocalRotation(data.localRotation);
+ model.setLocalScale(data.localScale);
+ return model;
+ }
+
+ private Limb createLimb(ModelData data) {
+ Limb limb = new Limb();
+ limb.name = data.name;
+ Quaternion rotation = data.preRotation.mult(data.localRotation);
+ limb.bindTransform = new Transform(data.localTranslation, rotation, data.localScale);
+ return limb;
+ }
+
+ private BindPose createPose(BindPoseData data) {
+ BindPose pose = new BindPose();
+ pose.name = data.name;
+ for(NodeTransformData item : data.list) {
+ Transform t = buildTransform(item.transform);
+ //t.getTranslation().divideLocal(unitSize);
+ t.getScale().multLocal(unitSize);
+ pose.nodeTransforms.put(item.nodeId, t);
+ }
+ return pose;
+ }
+
+ private Texture createTexture(TextureData data) {
+ Texture tex = new Texture2D();
+ tex.setName(data.name);
+ return tex;
+ }
+
+ private Image createImage(ImageData data) {
+ Image image = null;
+ if(data.filename != null) {
+ // Try load by absolute path
+ File file = new File(data.filename);
+ if(file.exists() && file.isFile()) {
+ File dir = new File(file.getParent());
+ String locatorPath = dir.getAbsolutePath();
+ Texture tex = null;
+ try {
+ assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
+ tex = assetManager.loadTexture(file.getName());
+ } catch(Exception e) {
+ } finally {
+ assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
+ }
+ if(tex != null)
+ image = tex.getImage();
+ }
+ }
+ if(image == null && data.relativeFilename != null) {
+ // Try load by relative path
+ File dir = new File(sceneFolderName);
+ String locatorPath = dir.getAbsolutePath();
+ Texture tex = null;
+ try {
+ assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
+ tex = assetManager.loadTexture(data.relativeFilename);
+ } catch(Exception e) {
+ } finally {
+ assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
+ }
+ if(tex != null)
+ image = tex.getImage();
+ }
+ if(image == null && data.content != null) {
+ // Try load from content
+ String filename = null;
+ if(data.filename != null)
+ filename = new File(data.filename).getName();
+ if(filename != null && data.relativeFilename != null)
+ filename = data.relativeFilename;
+ // Filename is required to aquire asset loader by extension
+ if(filename != null) {
+ String locatorPath = sceneFilename;
+ filename = sceneFilename + File.separatorChar + filename; // Unique path
+ Texture tex = null;
+ try {
+ assetManager.registerLocator(locatorPath, com.jme3.scene.plugins.fbx.ContentTextureLocator.class);
+ tex = assetManager.loadTexture(new ContentTextureKey(filename, data.content));
+ } catch(Exception e) {
+ } finally {
+ assetManager.unregisterLocator(locatorPath, com.jme3.scene.plugins.fbx.ContentTextureLocator.class);
+ }
+ if(tex != null)
+ image = tex.getImage();
+ }
+ }
+ if(image == null)
+ throw new AssetLoadException("Content not loaded for image " + data.name);
+ return image;
+ }
+
+ private Transform buildTransform(double[] transform) {
+ float[] m = new float[transform.length];
+ for(int i = 0; i < transform.length; ++i)
+ m[i] = (float) transform[i];
+ Matrix4f matrix = new Matrix4f(m);
+ Vector3f pos = matrix.toTranslationVector();
+ Quaternion rot = matrix.toRotationQuat();
+ Vector3f scale = matrix.toScaleVector();
+ return new Transform(pos, rot, scale);
+ }
+
+ private Quaternion quatFromBoneAngles(float xAngle, float yAngle, float zAngle) {
+ float angle;
+ float sinY, sinZ, sinX, cosY, cosZ, cosX;
+ angle = zAngle * 0.5f;
+ sinZ = FastMath.sin(angle);
+ cosZ = FastMath.cos(angle);
+ angle = yAngle * 0.5f;
+ sinY = FastMath.sin(angle);
+ cosY = FastMath.cos(angle);
+ angle = xAngle * 0.5f;
+ sinX = FastMath.sin(angle);
+ cosX = FastMath.cos(angle);
+ float cosYXcosZ = cosY * cosZ;
+ float sinYXsinZ = sinY * sinZ;
+ float cosYXsinZ = cosY * sinZ;
+ float sinYXcosZ = sinY * cosZ;
+ // For some reason bone space is differ, this is modified formulas
+ float w = (cosYXcosZ * cosX + sinYXsinZ * sinX);
+ float x = (cosYXcosZ * sinX - sinYXsinZ * cosX);
+ float y = (sinYXcosZ * cosX + cosYXsinZ * sinX);
+ float z = (cosYXsinZ * cosX - sinYXcosZ * sinX);
+ return new Quaternion(x, y, z, w).normalizeLocal();
+ }
+
+ private Node linkScene() {
+ logger.log(Level.FINE, "Linking scene objects");
+ long startTime = System.currentTimeMillis();
+ Node sceneNode = linkSceneNodes();
+ linkMaterials();
+ linkMeshes(sceneNode);
+ linkSkins(sceneNode);
+ linkAnimations();
+ long estimatedTime = System.currentTimeMillis() - startTime;
+ logger.log(Level.FINE, "Linking done in {0} ms", estimatedTime);
+ return sceneNode;
+ }
+
+ private Node linkSceneNodes() {
+ Node sceneNode = new Node(sceneName + "-scene");
+ // Build mesh nodes
+ for(long nodeId : modelDataMap.keySet()) {
+ ModelData data = modelDataMap.get(nodeId);
+ if(data.type.equals("Mesh")) {
+ Node node = createNode(data);
+ modelMap.put(nodeId, node);
+ }
+ }
+ // Link model nodes into scene
+ for(long modelId : modelMap.keySet()) {
+ List refs = refMap.get(modelId);
+ if(refs == null)
+ continue;
+ Node model = modelMap.get(modelId);
+ for(long refId : refs) {
+ Node rootNode = (refId != 0) ? modelMap.get(refId) : sceneNode;
+ if(rootNode != null)
+ rootNode.attachChild(model);
+ }
+ }
+ // Build bind poses
+ for(long poseId : poseDataMap.keySet()) {
+ BindPoseData data = poseDataMap.get(poseId);
+ BindPose pose = createPose(data);
+ if(pose != null)
+ bindMap.put(poseId, pose);
+ }
+ // Apply bind poses
+ for(BindPose pose : bindMap.values()) {
+ for(long nodeId : pose.nodeTransforms.keySet()) {
+ Node model = modelMap.get(nodeId);
+ if(model != null) {
+ Transform t = pose.nodeTransforms.get(nodeId);
+ model.setLocalTransform(t);
+ }
+ }
+ }
+ return sceneNode;
+ }
+
+ private void linkMaterials() {
+ // Build materials
+ for(long matId : matDataMap.keySet()) {
+ MaterialData data = matDataMap.get(matId);
+ Material material = createMaterial(data);
+ if(material != null)
+ matMap.put(matId, material);
+ }
+ // Build textures
+ for(long texId : texDataMap.keySet()) {
+ TextureData data = texDataMap.get(texId);
+ Texture tex = createTexture(data);
+ if(tex != null)
+ texMap.put(texId, tex);
+ }
+ // Build Images
+ for(long imgId : imgDataMap.keySet()) {
+ ImageData data = imgDataMap.get(imgId);
+ Image img = createImage(data);
+ if(img != null)
+ imgMap.put(imgId, img);
+ }
+ // Link images to textures
+ for(long imgId : imgMap.keySet()) {
+ List refs = refMap.get(imgId);
+ if(refs == null)
+ continue;
+ Image img = imgMap.get(imgId);
+ for(long refId : refs) {
+ Texture tex = texMap.get(refId);
+ if(tex != null)
+ tex.setImage(img);
+ }
+ }
+ // Link textures to material maps
+ for(long texId : texMap.keySet()) {
+ List props = propMap.get(texId);
+ if(props == null)
+ continue;
+ Texture tex = texMap.get(texId);
+ for(PropertyLink prop : props) {
+ Material mat = matMap.get(prop.ref);
+ if(mat != null) {
+ if(prop.propName.equals("DiffuseColor")) {
+ mat.setTexture("DiffuseMap", tex);
+ mat.setColor("Diffuse", ColorRGBA.White);
+ } else if(prop.propName.equals("SpecularColor")) {
+ mat.setTexture("SpecularMap", tex);
+ mat.setColor("Specular", ColorRGBA.White);
+ } else if(prop.propName.equals("NormalMap"))
+ mat.setTexture("NormalMap", tex);
+ }
+ }
+ }
+ }
+
+ private void linkMeshes(Node sceneNode) {
+ // Build meshes
+ for(long meshId : meshDataMap.keySet()) {
+ MeshData data = meshDataMap.get(meshId);
+ Geometry geom = createGeomerty(data);
+ if(geom != null)
+ geomMap.put(meshId, geom);
+ }
+ // Link meshes to models
+ for(long geomId : geomMap.keySet()) {
+ List refs = refMap.get(geomId);
+ if(refs == null)
+ continue;
+ Geometry geom = geomMap.get(geomId);
+ for(long refId : refs) {
+ Node rootNode = (refId != 0) ? modelMap.get(refId) : sceneNode;
+ if(rootNode != null) {
+ geom.setName(rootNode.getName() + "-mesh");
+ geom.updateModelBound();
+ rootNode.attachChild(geom);
+ break;
+ }
+ }
+ }
+ // Link materials to meshes
+ for(long matId : matMap.keySet()) {
+ List refs = refMap.get(matId);
+ if(refs == null)
+ continue;
+ Material mat = matMap.get(matId);
+ for(long refId : refs) {
+ Node rootNode = modelMap.get(refId);
+ if(rootNode != null) {
+ for(Spatial child : rootNode.getChildren())
+ child.setMaterial(mat);
+ }
+ }
+ }
+ }
+
+ private void linkSkins(Node sceneNode) {
+ // Build skeleton limbs
+ for(long nodeId : modelDataMap.keySet()) {
+ ModelData data = modelDataMap.get(nodeId);
+ if(data.type.equals("LimbNode")) {
+ Limb limb = createLimb(data);
+ limbMap.put(nodeId, limb);
+ }
+ }
+ if(limbMap.size() == 0)
+ return;
+ // Build skeleton bones
+ Map bones = new HashMap();
+ for(long limbId : limbMap.keySet()) {
+ Limb limb = limbMap.get(limbId);
+ Bone bone = new Bone(limb.name);
+ Transform t = limb.bindTransform;
+ bone.setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale());
+ bones.put(limbId, bone);
+ }
+ // Attach bones to roots
+ for(long limbId : limbMap.keySet()) {
+ List refs = refMap.get(limbId);
+ if(refs == null)
+ continue;
+ // Find root limb
+ long rootLimbId = 0L;
+ for(long refId : refs) {
+ if(limbMap.containsKey(refId)) {
+ rootLimbId = refId;
+ break;
+ }
+ }
+ if(rootLimbId != 0L) {
+ Bone bone = bones.get(limbId);
+ Bone root = bones.get(rootLimbId);
+ root.addChild(bone);
+ }
+ }
+ // Link bone clusters to skin
+ for(long clusterId : clusterMap.keySet()) {
+ List refs = refMap.get(clusterId);
+ if(refs == null)
+ continue;
+ for(long skinId : refs) {
+ if(skinMap.containsKey(skinId)) {
+ ClusterData data = clusterMap.get(clusterId);
+ data.skinId = skinId;
+ break;
+ }
+ }
+ }
+ // Build the skeleton
+ this.skeleton = new Skeleton(bones.values().toArray(new Bone[0]));
+ skeleton.setBindingPose();
+ for(long limbId : bones.keySet()) {
+ Bone bone = bones.get(limbId);
+ Limb limb = limbMap.get(limbId);
+ limb.boneIndex = skeleton.getBoneIndex(bone);
+ }
+ // Assign bones skinning to meshes
+ for(long skinId : skinMap.keySet()) {
+ // Find mesh by skin
+ Mesh mesh = null;
+ MeshData meshData = null;
+ for(long meshId : refMap.get(skinId)) {
+ Geometry g = geomMap.get(meshId);
+ if(g != null) {
+ meshData = meshDataMap.get(meshId);
+ mesh = g.getMesh();
+ break;
+ }
+ }
+ // Bind skinning indexes and weight
+ if(mesh != null && meshData != null) {
+ // Create bone buffers
+ FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(meshData.vCount * 4);
+ ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(meshData.vCount * 4);
+ mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeightData);
+ mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndicesData);
+ mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(Usage.CpuOnly);
+ mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(Usage.CpuOnly);
+ VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight);
+ VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex);
+ indicesHW.setUsage(Usage.CpuOnly); // Setting usage to CpuOnly so that the buffer is not send empty to the GPU
+ weightsHW.setUsage(Usage.CpuOnly);
+ mesh.setBuffer(weightsHW);
+ mesh.setBuffer(indicesHW);
+ // Accumulate skin bones influence into mesh buffers
+ boolean bonesLimitExceeded = false;
+ for(long limbId : bones.keySet()) {
+ // Search bone cluster for the given limb and skin
+ ClusterData cluster = null;
+ for(long clusterId : refMap.get(limbId)) {
+ ClusterData data = clusterMap.get(clusterId);
+ if(data != null && data.skinId == skinId) {
+ cluster = data;
+ break;
+ }
+ }
+ if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length)
+ continue;
+ Limb limb = limbMap.get(limbId);
+ if(limb.boneIndex > 255)
+ throw new AssetLoadException("Bone index can't be packed into byte");
+ for(int i = 0; i < cluster.indexes.length; ++i) {
+ int vertexIndex = cluster.indexes[i];
+ if(vertexIndex >= meshData.reverseVertexMap.size())
+ throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + meshData.reverseVertexMap.size());
+ List dstVertices = meshData.reverseVertexMap.get(vertexIndex);
+ for(int v : dstVertices) {
+ // Append bone index and weight to vertex
+ int offset;
+ float w = 0;
+ for(offset = v * 4; offset < v * 4 + 4; ++offset) {
+ w = boneWeightData.get(offset);
+ if(w == 0)
+ break;
+ }
+ if(w == 0) {
+ boneWeightData.put(offset, (float) cluster.weights[i]);
+ boneIndicesData.put(offset, (byte) limb.boneIndex);
+ } else {
+ // TODO It would be nice to discard small weight to accumulate more heavy influence
+ bonesLimitExceeded = true;
+ }
+ }
+ }
+ }
+ if(bonesLimitExceeded)
+ logger.log(Level.WARNING, "Skinning support max 4 bone per vertex. Exceeding data will be discarded.");
+ // Postprocess bones weights
+ int maxWeightsPerVert = 0;
+ boneWeightData.rewind();
+ for(int v = 0; v < meshData.vCount; v++) {
+ float w0 = boneWeightData.get();
+ float w1 = boneWeightData.get();
+ float w2 = boneWeightData.get();
+ float w3 = boneWeightData.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) {
+ // normalize weights
+ float mult = (sum != 0) ? (1f / sum) : 0;
+ boneWeightData.position(v * 4);
+ boneWeightData.put(w0 * mult);
+ boneWeightData.put(w1 * mult);
+ boneWeightData.put(w2 * mult);
+ boneWeightData.put(w3 * mult);
+ }
+ }
+ mesh.setMaxNumWeights(maxWeightsPerVert);
+ mesh.generateBindPose(true);
+ }
+ }
+ // Attach controls
+ animControl = new AnimControl(skeleton);
+ sceneNode.addControl(animControl);
+ SkeletonControl control = new SkeletonControl(skeleton);
+ sceneNode.addControl(control);
+ }
+
+ private void linkAnimations() {
+ if(skeleton == null)
+ return;
+ AnimationList animList = key.getAnimations();
+ if(animList == null || animList.list.size() == 0)
+ return;
+ // Link curves to nodes
+ for(long curveId : acurveMap.keySet()) {
+ List props = propMap.get(curveId);
+ if(props == null)
+ continue;
+ AnimCurveData acurve = acurveMap.get(curveId);
+ for(PropertyLink prop : props) {
+ AnimNode anode = anodeMap.get(prop.ref);
+ if(anode != null) {
+ if(prop.propName.equals("d|X"))
+ anode.xCurve = acurve;
+ else if(prop.propName.equals("d|Y"))
+ anode.yCurve = acurve;
+ else if(prop.propName.equals("d|Z"))
+ anode.zCurve = acurve;
+ }
+ }
+ }
+ // Link nodes to layers
+ for(long nodeId : anodeMap.keySet()) {
+ List refs = refMap.get(nodeId);
+ if(refs == null)
+ continue;
+ for(long layerId : refs) {
+ if(alayerMap.containsKey(layerId)) {
+ AnimNode anode = anodeMap.get(nodeId);
+ anode.layerId = layerId;
+ break;
+ }
+ }
+ }
+ // Extract aminations
+ HashMap anims = new HashMap();
+ for(AnimInverval animInfo : animList.list) {
+ float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate;
+ float animStart = animInfo.firstFrame / this.animFrameRate;
+ float animStop = animInfo.lastFrame / this.animFrameRate;
+ Animation anim = new Animation(animInfo.name, length);
+ // Search source layer for animation nodes
+ long sourceLayerId = 0L;
+ for(long layerId : alayerMap.keySet()) {
+ AnimLayer layer = alayerMap.get(layerId);
+ if(layer.name.equals(animInfo.layerName)) {
+ sourceLayerId = layerId;
+ break;
+ }
+ }
+ // Assign animation nodes to limbs
+ for(Limb limb : limbMap.values()) {
+ limb.animTranslation = null;
+ limb.animRotation = null;
+ limb.animScale = null;
+ }
+ for(long nodeId : anodeMap.keySet()) {
+ List props = propMap.get(nodeId);
+ if(props == null)
+ continue;
+ AnimNode anode = anodeMap.get(nodeId);
+ if(sourceLayerId != 0L && anode.layerId != sourceLayerId)
+ continue; // filter node
+ for(PropertyLink prop : props) {
+ Limb limb = limbMap.get(prop.ref);
+ if(limb != null) {
+ if(prop.propName.equals("Lcl Translation"))
+ limb.animTranslation = anode;
+ else if(prop.propName.equals("Lcl Rotation"))
+ limb.animRotation = anode;
+ else if(prop.propName.equals("Lcl Scaling"))
+ limb.animScale = anode;
+ }
+ }
+ }
+ // Build bone tracks
+ for(Limb limb : limbMap.values()) {
+ long[] keyTimes = null;
+ boolean haveTranslation = (limb.animTranslation != null && limb.animTranslation.xCurve != null && limb.animTranslation.yCurve != null && limb.animTranslation.zCurve != null);
+ boolean haveRotation = (limb.animRotation != null && limb.animRotation.xCurve != null && limb.animRotation.yCurve != null && limb.animRotation.zCurve != null);
+ boolean haveScale = (limb.animScale != null && limb.animScale.xCurve != null && limb.animScale.yCurve != null && limb.animScale.zCurve != null);
+ // Search key time array
+ if(haveTranslation)
+ keyTimes = limb.animTranslation.xCurve.keyTimes;
+ else if(haveRotation)
+ keyTimes = limb.animRotation.xCurve.keyTimes;
+ else if(haveScale)
+ keyTimes = limb.animScale.xCurve.keyTimes;
+ if(keyTimes == null)
+ continue;
+ // Calculate keys interval by animation time interval
+ int firstKey = 0;
+ int lastKey = keyTimes.length - 1;
+ for(int i = 0; i < keyTimes.length; ++i) {
+ float time = (float) (((double) keyTimes[i]) * secondsPerUnit); // Translate into seconds
+ if(time <= animStart)
+ firstKey = i;
+ if(time >= animStop) {
+ lastKey = i;
+ break;
+ }
+ }
+ int keysCount = lastKey - firstKey + 1;
+ if(keysCount <= 0)
+ continue;
+ float[] times = new float[keysCount];
+ Vector3f[] translations = new Vector3f[keysCount];
+ Quaternion[] rotations = new Quaternion[keysCount];
+ Vector3f[] scales = null;
+ // Calculate keyframes times
+ for(int i = 0; i < keysCount; ++i) {
+ int keyIndex = firstKey + i;
+ float time = (float) (((double) keyTimes[keyIndex]) * secondsPerUnit); // Translate into seconds
+ times[i] = time - animStart;
+ }
+ // Load keyframes from animation curves
+ if(haveTranslation) {
+ for(int i = 0; i < keysCount; ++i) {
+ int keyIndex = firstKey + i;
+ float x = limb.animTranslation.xCurve.keyValues[keyIndex] - limb.animTranslation.value.x;
+ float y = limb.animTranslation.yCurve.keyValues[keyIndex] - limb.animTranslation.value.y;
+ float z = limb.animTranslation.zCurve.keyValues[keyIndex] - limb.animTranslation.value.z;
+ translations[i] = new Vector3f(x, y, z).divideLocal(unitSize);
+ }
+ } else {
+ for(int i = 0; i < keysCount; ++i)
+ translations[i] = new Vector3f();
+ }
+ if(haveRotation) {
+ for(int i = 0; i < keysCount; ++i) {
+ int keyIndex = firstKey + i;
+ float x = limb.animRotation.xCurve.keyValues[keyIndex];
+ float y = limb.animRotation.yCurve.keyValues[keyIndex];
+ float z = limb.animRotation.zCurve.keyValues[keyIndex];
+ rotations[i] = new Quaternion().fromAngles(FastMath.DEG_TO_RAD * x, FastMath.DEG_TO_RAD * y, FastMath.DEG_TO_RAD * z);
+ }
+ } else {
+ for(int i = 0; i < keysCount; ++i)
+ rotations[i] = new Quaternion();
+ }
+ if(haveScale) {
+ scales = new Vector3f[keysCount];
+ for(int i = 0; i < keysCount; ++i) {
+ int keyIndex = firstKey + i;
+ float x = limb.animScale.xCurve.keyValues[keyIndex];
+ float y = limb.animScale.yCurve.keyValues[keyIndex];
+ float z = limb.animScale.zCurve.keyValues[keyIndex];
+ scales[i] = new Vector3f(x, y, z);
+ }
+ }
+ BoneTrack track = null;
+ if(haveScale)
+ track = new BoneTrack(limb.boneIndex, times, translations, rotations, scales);
+ else
+ track = new BoneTrack(limb.boneIndex, times, translations, rotations);
+ anim.addTrack(track);
+ }
+ anims.put(anim.getName(), anim);
+ }
+ animControl.setAnimations(anims);
+ }
+
+ private void releaseObjects() {
+ meshDataMap.clear();
+ matDataMap.clear();
+ texDataMap.clear();
+ imgDataMap.clear();
+ modelDataMap.clear();
+ poseDataMap.clear();
+ skinMap.clear();
+ clusterMap.clear();
+ acurveMap.clear();
+ anodeMap.clear();
+ alayerMap.clear();
+ refMap.clear();
+ propMap.clear();
+ modelMap.clear();
+ limbMap.clear();
+ bindMap.clear();
+ geomMap.clear();
+ matMap.clear();
+ texMap.clear();
+ imgMap.clear();
+ skeleton = null;
+ animControl = null;
+ key = null;
+ }
+
+ private class MeshData {
+ double[] vertices;
+ int[] indices;
+ int[] edges;
+ String normalsMapping;
+ String normalsReference;
+ double[] normals;
+ String tangentsMapping;
+ String tangentsReference;
+ double[] tangents;
+ String binormalsMapping;
+ String binormalsReference;
+ double[] binormals;
+ String uvMapping;
+ String uvReference;
+ double[] uv;
+ int[] uvIndex;
+ String smoothingMapping;
+ String smoothingReference;
+ int[] smoothing;
+ String materialsMapping;
+ String materialsReference;
+ int[] materials;
+ // Build helping data
+ int iCount;
+ int vCount;
+ int srcVertexCount;
+ int[] vertexMap; // Target vertex -> source vertex
+ List> reverseVertexMap; // source vertex -> list of target vertices
+ int[] indexMap; // Target vertex -> source index
+ }
+
+ private class ModelData {
+ String name;
+ String type;
+ Vector3f localTranslation = new Vector3f();
+ Quaternion localRotation = new Quaternion();
+ Vector3f localScale = new Vector3f(Vector3f.UNIT_XYZ);
+ Quaternion preRotation = new Quaternion();
+ }
+
+ private class NodeTransformData {
+ long nodeId;
+ double[] transform;
+ }
+
+ private class BindPoseData {
+ String name;
+ List list = new LinkedList();
+ }
+
+ private class BindPose {
+ String name;
+ Map nodeTransforms = new HashMap();
+ }
+
+ private class MaterialData {
+ String name;
+ String shadingModel = "phong";
+ Vector3f ambientColor = new Vector3f(0.2f, 0.2f, 0.2f);
+ float ambientFactor = 1.0f;
+ Vector3f diffuseColor = new Vector3f(0.8f, 0.8f, 0.8f);
+ float diffuseFactor = 1.0f;
+ Vector3f specularColor = new Vector3f(0.2f, 0.2f, 0.2f);
+ float specularFactor = 1.0f;
+ float shininessExponent = 20.0f;
+ }
+
+ private class TextureData {
+ String name;
+ String bindType;
+ String filename;
+ }
+
+ private class ImageData {
+ String name;
+ String type;
+ String filename;
+ String relativeFilename;
+ byte[] content;
+ }
+
+ private class Limb {
+ String name;
+ Transform bindTransform;
+ int boneIndex;
+ AnimNode animTranslation;
+ AnimNode animRotation;
+ AnimNode animScale;
+ }
+
+ private class ClusterData {
+ int[] indexes;
+ double[] weights;
+ double[] transform;
+ double[] transformLink;
+ long skinId;
+ }
+
+ private class SkinData {
+ String type;
+ }
+
+ private class AnimCurveData {
+ long[] keyTimes;
+ float[] keyValues;
+ }
+
+ private class AnimLayer {
+ String name;
+ }
+
+ private class AnimNode {
+ String name;
+ Vector3f value;
+ AnimCurveData xCurve;
+ AnimCurveData yCurve;
+ AnimCurveData zCurve;
+ long layerId;
+ }
+
+ private class PropertyLink {
+ long ref;
+ String propName;
+
+ public PropertyLink(long id, String prop) {
+ this.ref = id;
+ this.propName = prop;
+ }
+ }
+
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java
new file mode 100644
index 000000000..eeeee872c
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FBXElement.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2014 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.fbx.file;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FBXElement {
+
+ public String id;
+ public List