From f65dd517c904480239152826d7bbc4f36afdc8d3 Mon Sep 17 00:00:00 2001 From: Eirenliel Allier Date: Tue, 26 Apr 2016 10:10:26 +0300 Subject: [PATCH 1/4] Reworked original FBX importer --- .../jme3/scene/plugins/fbx/RotationOrder.java | 102 + .../jme3/scene/plugins/fbx/SceneLoader.java | 1641 +++-------------- .../plugins/fbx/objects/FbxAnimCurve.java | 49 + .../plugins/fbx/objects/FbxAnimNode.java | 86 + .../plugins/fbx/objects/FbxBindPose.java | 55 + .../scene/plugins/fbx/objects/FbxCluster.java | 42 + .../scene/plugins/fbx/objects/FbxImage.java | 123 ++ .../plugins/fbx/objects/FbxMaterial.java | 113 ++ .../scene/plugins/fbx/objects/FbxMesh.java | 537 ++++++ .../scene/plugins/fbx/objects/FbxNode.java | 274 +++ .../scene/plugins/fbx/objects/FbxObject.java | 40 + .../scene/plugins/fbx/objects/FbxSkin.java | 150 ++ .../scene/plugins/fbx/objects/FbxTexture.java | 42 + 13 files changed, 1875 insertions(+), 1379 deletions(-) create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java new file mode 100644 index 000000000..94940609f --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java @@ -0,0 +1,102 @@ +package com.jme3.scene.plugins.fbx; + +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; + +// TODO This class has some potential in it... Should investigate +public enum RotationOrder { + + EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ; + + public static final RotationOrder[] values = values(); + + private RotationOrder() { + } + + public Quaternion rotate(Vector3f vec) { + return fromEuler(vec.x * FastMath.DEG_TO_RAD, vec.y * FastMath.DEG_TO_RAD, vec.z * FastMath.DEG_TO_RAD, this); + } + + public Quaternion rotate(float x, float y, float z) { + return fromEuler(x * FastMath.DEG_TO_RAD, y * FastMath.DEG_TO_RAD, z * FastMath.DEG_TO_RAD, this); + } + + private static Quaternion fromEuler(float x, float y, float z, RotationOrder order) { + switch(order) { + case EULER_XYZ: + return toQuat(x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z); + case EULER_YXZ: + return toQuat(y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z); + case EULER_ZXY: + return toQuat(z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y); + case EULER_ZYX: + return toQuat(z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X); + case EULER_YZX: + return toQuat(y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X); + case EULER_XZY: + return toQuat(x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y); + case SPHERIC_XYZ: + throw new IllegalArgumentException("Spheric rotation is unsupported in this importer"); + } + /*float c1 = FastMath.cos( x / 2 ); + float c2 = FastMath.cos( y / 2 ); + float c3 = FastMath.cos( z / 2 ); + float s1 = FastMath.sin( x / 2 ); + float s2 = FastMath.sin( y / 2 ); + float s3 = FastMath.sin( z / 2 ); + + float _x; + float _y; + float _z; + float _w; + + switch(order) { + case EULER_XYZ: + _x = s1 * c2 * c3 + c1 * s2 * s3; + _y = c1 * s2 * c3 - s1 * c2 * s3; + _z = c1 * c2 * s3 + s1 * s2 * c3; + _w = c1 * c2 * c3 - s1 * s2 * s3; + return new Quaternion(_x, _y, _z, _w); + case EULER_YXZ: + _x = s1 * c2 * c3 + c1 * s2 * s3; + _y = c1 * s2 * c3 - s1 * c2 * s3; + _z = c1 * c2 * s3 - s1 * s2 * c3; + _w = c1 * c2 * c3 + s1 * s2 * s3; + return new Quaternion(_x, _y, _z, _w); + case EULER_ZXY: + _x = s1 * c2 * c3 - c1 * s2 * s3; + _y = c1 * s2 * c3 + s1 * c2 * s3; + _z = c1 * c2 * s3 + s1 * s2 * c3; + _w = c1 * c2 * c3 - s1 * s2 * s3; + return new Quaternion(_x, _y, _z, _w); + case EULER_ZYX: + _x = s1 * c2 * c3 - c1 * s2 * s3; + _y = c1 * s2 * c3 + s1 * c2 * s3; + _z = c1 * c2 * s3 - s1 * s2 * c3; + _w = c1 * c2 * c3 + s1 * s2 * s3; + return new Quaternion(_x, _y, _z, _w); + case EULER_YZX: + _x = s1 * c2 * c3 + c1 * s2 * s3; + _y = c1 * s2 * c3 + s1 * c2 * s3; + _z = c1 * c2 * s3 - s1 * s2 * c3; + _w = c1 * c2 * c3 - s1 * s2 * s3; + return new Quaternion(_x, _y, _z, _w); + case EULER_XZY: + _x = s1 * c2 * c3 - c1 * s2 * s3; + _y = c1 * s2 * c3 - s1 * c2 * s3; + _z = c1 * c2 * s3 + s1 * s2 * c3; + _w = c1 * c2 * c3 + s1 * s2 * s3; + return new Quaternion(_x, _y, _z, _w); + } + */ + throw new AssertionError("Impossible"); + } + + private static Quaternion toQuat(float ax1v, Vector3f ax1, float ax2v, Vector3f ax2, float ax3v, Vector3f ax3) { + Quaternion q1 = new Quaternion().fromAngleNormalAxis(ax1v, ax1); + Quaternion q2 = new Quaternion().fromAngleNormalAxis(ax2v, ax2); + Quaternion q3 = new Quaternion().fromAngleNormalAxis(ax3v, ax3); + return q1.multLocal(q2).multLocal(q3); + } +} 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 index 87e13abed..cf4af7682 100644 --- 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 @@ -1,46 +1,13 @@ -/* - * 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.Set; +import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; @@ -50,36 +17,29 @@ import com.jme3.animation.Bone; import com.jme3.animation.BoneTrack; import com.jme3.animation.Skeleton; import com.jme3.animation.SkeletonControl; +import com.jme3.animation.Track; 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.asset.ModelKey; -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; +import com.jme3.scene.plugins.fbx.objects.FbxAnimCurve; +import com.jme3.scene.plugins.fbx.objects.FbxAnimNode; +import com.jme3.scene.plugins.fbx.objects.FbxBindPose; +import com.jme3.scene.plugins.fbx.objects.FbxCluster; +import com.jme3.scene.plugins.fbx.objects.FbxImage; +import com.jme3.scene.plugins.fbx.objects.FbxMaterial; +import com.jme3.scene.plugins.fbx.objects.FbxMesh; +import com.jme3.scene.plugins.fbx.objects.FbxNode; +import com.jme3.scene.plugins.fbx.objects.FbxObject; +import com.jme3.scene.plugins.fbx.objects.FbxSkin; +import com.jme3.scene.plugins.fbx.objects.FbxTexture; /** * FBX file format loader @@ -91,53 +51,50 @@ import com.jme3.util.BufferUtils; public class SceneLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); + private static final double secondsPerUnit = 1 / 46186158000d; // Animation speed factor + public static final boolean WARN_IGNORED_ATTRIBUTES = false; - private AssetManager assetManager; + // Scene loading data + private List warnings = new ArrayList<>(); private AnimationList animList; - private String sceneName; - private String sceneFilename; - private String sceneFolderName; - private float unitSize; - private float animFrameRate; - private final double secondsPerUnit = 1 / 46186158000d; // Animation speed factor + public String sceneFilename; + public String sceneFolderName; + public AssetManager assetManager; + public AssetInfo currentAssetInfo; - // 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 global settings + private float animFrameRate; + public float unitSize; + public int xAxis = 1; + public int yAxis = 1; + public int zAxis = 1; // 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 Map allObjects = new HashMap<>(); // All supported FBX objects + private Map skinMap = new HashMap<>(); // Skin for bone clusters + private Map alayerMap = new HashMap<>(); // Amination layers + public Map modelMap = new HashMap<>(); // Nodes + private Map limbMap = new HashMap<>(); // Nodes that are actually bones + private Map bindMap = new HashMap<>(); // Node bind poses + private Map geomMap = new HashMap<>(); // Mesh geometries private Skeleton skeleton; private AnimControl animControl; + public Node sceneNode; + + public void warning(String warning) { + warnings.add(warning); + } @Override public Object load(AssetInfo assetInfo) throws IOException { + this.currentAssetInfo = assetInfo; this.assetManager = assetInfo.getManager(); AssetKey assetKey = assetInfo.getKey(); if(assetKey instanceof SceneKey) animList = ((SceneKey) assetKey).getAnimations(); - else if(!(assetKey instanceof ModelKey)) - throw new AssetLoadException("Invalid asset key"); InputStream stream = assetInfo.openStream(); - Node sceneNode = null; + final Node sceneNode = this.sceneNode = new Node(sceneName + "-scene"); try { sceneFilename = assetKey.getName(); sceneFolderName = assetKey.getFolder(); @@ -145,413 +102,131 @@ public class SceneLoader implements AssetLoader { 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(); + linkScene(); + if(warnings.size() > 0) + logger.log(Level.WARNING, "Model load finished with warnings:\n" + String.join("\n", warnings)); } finally { releaseObjects(); - if(stream != null) { + 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")) + // Is it possible for elements to be in wrong order? + switch(e.id) { + case "GlobalSettings": loadGlobalSettings(e); - else if(e.id.equals("Objects")) + break; + case "Objects": loadObjects(e); - else if(e.id.equals("Connections")) + break; + case "Connections": loadConnections(e); + break; + } } long estimatedTime = System.currentTimeMillis() - startTime; logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime); } private void loadGlobalSettings(FbxElement element) { + for(FbxElement e2 : element.getFbxProperties()) { + String propName = (String) e2.properties.get(0); + switch(propName) { + case "UnitScaleFactor": + this.unitSize = ((Double) e2.properties.get(4)).floatValue(); + break; + case "CustomFrameRate": + float framerate = ((Double) e2.properties.get(4)).floatValue(); + if(framerate != -1) + this.animFrameRate = framerate; + break; + case "UpAxisSign": + this.yAxis = ((Integer) e2.properties.get(4)).intValue(); + break; + case "FrontAxisSign": + this.zAxis = ((Integer) e2.properties.get(4)).intValue(); + break; + case "CoordAxisSign": + this.xAxis = ((Integer) e2.properties.get(4)).intValue(); + break; + } + } + } + + private void loadObjects(FbxElement element) throws IOException { + FbxObject obj = 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("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); + switch(e.id) { + case "Geometry": + FbxMesh mesh = new FbxMesh(this, e); + obj = mesh; + if(mesh.geometries != null) + geomMap.put(mesh.id, mesh); + break; + case "Material": + obj = new FbxMaterial(this, e); + break; + case "Model": + FbxNode node = new FbxNode(this, e); + obj = node; + modelMap.put(node.id, node); + if(node.isLimb()) + limbMap.put(node.id, node); + break; + case "Pose": + FbxBindPose pose = new FbxBindPose(this, e); + obj = pose; + bindMap.put(pose.id, pose); + break; + case "Texture": + obj = new FbxTexture(this, e); + break; + case "Video": + obj = new FbxImage(this, e); + break; + case "Deformer": + obj = loadDeformer(e); + break; + case "AnimationLayer": + FbxObject layer = new FbxObject(this, e); + obj = layer; + alayerMap.put(layer.id, layer); + break; + case "AnimationCurve": + obj = new FbxAnimCurve(this, e); + break; + case "AnimationCurveNode": + obj = new FbxAnimNode(this, e); + break; + default: + obj = null; + //warnings.add("Object with id '" + e.id + "' was ignored"); + } + if(obj != null) + allObjects.put(obj.id, obj); + } + } + + private FbxObject loadDeformer(FbxElement element) { 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); - } + switch(type) { + case "Skin": + FbxSkin skinData = new FbxSkin(this, element); + skinMap.put(skinData.id, skinData); + return skinData; + case "Cluster": + FbxCluster clusterData = new FbxCluster(this, element); + return clusterData; } + return null; } private void loadConnections(FbxElement element) { @@ -559,698 +234,65 @@ public class SceneLoader implements AssetLoader { if(e.id.equals("C")) { String type = (String) e.properties.get(0); long objId, refId; - if(type.equals("OO")) { + FbxObject obj, ref; + switch(type) { + case "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); + obj = allObjects.get(objId); + ref = allObjects.get(refId); + if(ref != null) { + ref.link(obj); + } else if(refId == 0) { + obj.linkToZero(); } - links.add(refId); - } else if(type.equals("OP")) { + break; + case "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 = index >= 0 ? (float) data.uv[2 * index + 0] : 0; - float v = index >= 0 ? (float) data.uv[2 * index + 1] : 0; - 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); - 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); + obj = allObjects.get(objId); + ref = allObjects.get(refId); + if(ref != null) + ref.link(obj, propName); + break; } - 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() { + private void linkScene() { logger.log(Level.FINE, "Linking scene objects"); long startTime = System.currentTimeMillis(); - Node sceneNode = linkSceneNodes(); - linkMaterials(); - linkMeshes(sceneNode); - linkSkins(sceneNode); - linkAnimations(); + applySkinning(); + buildAnimations(); + for(FbxMesh mesh : geomMap.values()) + mesh.clearMaterials(); + // Remove bones from node structures : JME creates attach node by itself + for(FbxNode limb : limbMap.values()) + limb.node.removeFromParent(); 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); - 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) + private void applySkinning() { + for(FbxBindPose pose : bindMap.values()) + pose.fillBindTransforms(); + if(limbMap == null) 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); + List bones = new ArrayList<>(); + for(FbxNode limb : limbMap.values()) { + if(limb.bone != null) { + bones.add(limb.bone); + limb.buildBindPoseBoneTransform(); } } - // 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 = new Skeleton(bones.toArray(new Bone[bones.size()])); 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); - } - } + for(FbxNode limb : limbMap.values()) + limb.setSkeleton(skeleton); + for(FbxSkin skin : skinMap.values()) + skin.generateSkinning(); // Attach controls animControl = new AnimControl(skeleton); sceneNode.addControl(animControl); @@ -1258,45 +300,20 @@ public class SceneLoader implements AssetLoader { sceneNode.addControl(control); } - private void linkAnimations() { + private void buildAnimations() { if(skeleton == null) return; - 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; - } + if(animList == null || animList.list.size() == 0) { + animList = new AnimationList(); + for(long layerId : alayerMap.keySet()) { + FbxObject layer = alayerMap.get(layerId); + animList.add(layer.name, layer.name, 0, -1); } } // Extract aminations HashMap anims = new HashMap(); for(AnimInverval animInfo : animList.list) { + float realLength = 0; float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate; float animStart = animInfo.firstFrame / this.animFrameRate; float animStop = animInfo.lastFrame / this.animFrameRate; @@ -1304,65 +321,50 @@ public class SceneLoader implements AssetLoader { // Search source layer for animation nodes long sourceLayerId = 0L; for(long layerId : alayerMap.keySet()) { - AnimLayer layer = alayerMap.get(layerId); + FbxObject 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 + for(FbxNode limb : limbMap.values()) { + // Animation channels may have different keyframes (non-baked animation). + // So we have to restore intermediate values for all channels cause of JME requires + // a bone track as a single channel with collective transformation for each keyframe + Set stamps = new TreeSet(); // Sorted unique timestamps + FbxAnimNode animTranslation = limb.animTranslation(sourceLayerId); + FbxAnimNode animRotation = limb.animRotation(sourceLayerId); + FbxAnimNode animScale = limb.animScale(sourceLayerId); + boolean haveTranslation = haveAnyChannel(animTranslation); + boolean haveRotation = haveAnyChannel(animRotation); + boolean haveScale = haveAnyChannel(animScale); + // Collect keyframes stamps 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) + animTranslation.exportTimes(stamps); + if(haveRotation) + animRotation.exportTimes(stamps); + if(haveScale) + animScale.exportTimes(stamps); + if(stamps.isEmpty()) continue; + long[] keyTimes = new long[stamps.size()]; + int cnt = 0; + for(long t : stamps) + keyTimes[cnt++] = t; // Calculate keys interval by animation time interval - int firstKey = 0; - int lastKey = keyTimes.length - 1; + int firstKeyIndex = 0; + int lastKeyIndex = 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; + firstKeyIndex = i; + if(time >= animStop && animStop >= 0) { + lastKeyIndex = i; break; } } - int keysCount = lastKey - firstKey + 1; + int keysCount = lastKeyIndex - firstKeyIndex + 1; if(keysCount <= 0) continue; float[] times = new float[keysCount]; @@ -1371,43 +373,42 @@ public class SceneLoader implements AssetLoader { Vector3f[] scales = null; // Calculate keyframes times for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKey + i; + int keyIndex = firstKeyIndex + i; float time = (float) (((double) keyTimes[keyIndex]) * secondsPerUnit); // Translate into seconds times[i] = time - animStart; + realLength = Math.max(realLength, times[i]); } // 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); + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animTranslation; + Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value).subtractLocal(n.value); // Why do it here but not in other places? FBX magic? + translations[i] = tvec.divideLocal(unitSize); } } else { for(int i = 0; i < keysCount; ++i) - translations[i] = new Vector3f(); + translations[i] = Vector3f.ZERO; } + RotationOrder ro = RotationOrder.EULER_XYZ; 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); + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animRotation; + Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value); + rotations[i] = ro.rotate(tvec); } } else { for(int i = 0; i < keysCount; ++i) - rotations[i] = new Quaternion(); + rotations[i] = Quaternion.IDENTITY; } 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); + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animScale; + Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value); + scales[i] = tvec; } } BoneTrack track = null; @@ -1417,166 +418,48 @@ public class SceneLoader implements AssetLoader { track = new BoneTrack(limb.boneIndex, times, translations, rotations); anim.addTrack(track); } + if(realLength != length && animInfo.lastFrame == -1) { + Track[] tracks = anim.getTracks(); + if(tracks == null || tracks.length == 0) + continue; + anim = new Animation(animInfo.name, realLength); + for(Track track : tracks) + anim.addTrack(track); + } anims.put(anim.getName(), anim); } animControl.setAnimations(anims); } + private static boolean haveAnyChannel(FbxAnimNode anims) { + return anims != null && anims.haveAnyChannel(); + } + private void releaseObjects() { - meshDataMap.clear(); - matDataMap.clear(); - texDataMap.clear(); - imgDataMap.clear(); - modelDataMap.clear(); - poseDataMap.clear(); + // Reset settings + unitSize = 1; + animFrameRate = 30; + xAxis = 1; + yAxis = 1; + zAxis = 1; + // Clear cache + warnings.clear(); + animList = null; + sceneName = null; + sceneFilename = null; + sceneFolderName = null; + assetManager = null; + currentAssetInfo = null; + // Clear objects + allObjects.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; - animList = 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 + sceneNode = null; } - - 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/objects/FbxAnimCurve.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java new file mode 100644 index 000000000..395216156 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java @@ -0,0 +1,49 @@ +package com.jme3.scene.plugins.fbx.objects; + +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxAnimCurve extends FbxObject { + + public long[] keyTimes; + public float[] keyValues; + public float defaultValue = 0.0f; + + public FbxAnimCurve(SceneLoader scene, FbxElement element) { + super(scene, element); + for(FbxElement e : element.children) { + switch(e.id) { + case "KeyTime": + keyTimes = (long[]) e.properties.get(0); + break; + case "KeyValueFloat": + keyValues = (float[]) e.properties.get(0); + break; + case "Default": + defaultValue = ((Number) e.properties.get(0)).floatValue(); + break; + } + } + } + + public float getValue(long time) { + // Search animation interval + for(int i = 0; i < keyTimes.length; ++i) { + if(keyTimes[i] == time) { // hit the keyframe + return keyValues[i]; + } else if(keyTimes[i] > time) { + if(i == 0) { // left from the whole range + return defaultValue;//keyValues[0]; + } else { + // Interpolate between two keyframes + float dt = (float) (keyTimes[i] - keyTimes[i - 1]); + float dtInt = (float) (time - keyTimes[i - 1]); + float dv = keyValues[i] - keyValues[i - 1]; + return keyValues[i - 1] + dv * (dtInt / dt); + } + } + } + // right from the whole range + return defaultValue;//keyValues[keyValues.length - 1]; + } +} \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java new file mode 100644 index 000000000..7febc92d1 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java @@ -0,0 +1,86 @@ +package com.jme3.scene.plugins.fbx.objects; + +import java.util.Collection; + +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxAnimNode extends FbxObject { + + public Vector3f value; + public FbxAnimCurve xCurve; + public FbxAnimCurve yCurve; + public FbxAnimCurve zCurve; + public long layerId; + + public FbxAnimNode(SceneLoader scene, FbxElement element) { + super(scene, element); + if(type.equals("")) { + Double x = null, y = null, z = null; + for(FbxElement e2 : element.getFbxProperties()) { + String propName = (String) e2.properties.get(0); + switch(propName) { + case "d|X": + x = (Double) e2.properties.get(4); + break; + case "d|Y": + y = (Double) e2.properties.get(4); + break; + case "d|Z": + z = (Double) e2.properties.get(4); + break; + } + } + // Load only T R S curve nodes + if(x != null && y != null && z != null) + value = new Vector3f(x.floatValue(), y.floatValue(), z.floatValue()); + } + } + + @Override + public void link(FbxObject otherObject, String propertyName) { + if(otherObject instanceof FbxAnimCurve) { + FbxAnimCurve curve = (FbxAnimCurve) otherObject; + switch(propertyName) { + case "d|X": + xCurve = curve; + break; + case "d|Y": + yCurve = curve; + break; + case "d|Z": + zCurve = curve; + break; + } + } + } + + @Override + public void link(FbxObject otherObject) { + layerId = otherObject.id; + } + + public boolean haveAnyChannel() { + return xCurve != null || yCurve != null || zCurve != null; + } + + public void exportTimes(Collection stamps) { + if(xCurve != null) + for(long t : xCurve.keyTimes) + stamps.add(t); + if(yCurve != null) + for(long t : yCurve.keyTimes) + stamps.add(t); + if(zCurve != null) + for(long t : zCurve.keyTimes) + stamps.add(t); + } + + public Vector3f getValue(long time, Vector3f defaultValue) { + float xValue = (xCurve != null) ? xCurve.getValue(time) : defaultValue.x; + float yValue = (yCurve != null) ? yCurve.getValue(time) : defaultValue.y; + float zValue = (zCurve != null) ? zCurve.getValue(time) : defaultValue.z; + return new Vector3f(xValue, yValue, zValue); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java new file mode 100644 index 000000000..1170f9718 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java @@ -0,0 +1,55 @@ +package com.jme3.scene.plugins.fbx.objects; + +import java.util.HashMap; +import java.util.Map; + +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxBindPose extends FbxObject { + + public Map nodeTransforms = new HashMap<>(); + + public FbxBindPose(SceneLoader scene, FbxElement element) { + super(scene, element); + if(type.equals("BindPose")) { + for(FbxElement e : element.children) { + if(e.id.equals("PoseNode")) { + long nodeId = 0; + double[] transform = null; + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "Node": + nodeId = (Long) e2.properties.get(0); + break; + case "Matrix": + transform = (double[]) e2.properties.get(0); + break; + } + } + Matrix4f t = buildTransform(transform); + t.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize)); + nodeTransforms.put(nodeId, t); + } + } + } + } + + public void fillBindTransforms() { + for(long nodeId : nodeTransforms.keySet()) { + FbxNode node = scene.modelMap.get(nodeId); + node.bindTransform = nodeTransforms.get(nodeId).clone(); + } + } + + private static Matrix4f 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(); + matrix.set(m, false); + return matrix; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java new file mode 100644 index 000000000..354354567 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java @@ -0,0 +1,42 @@ +package com.jme3.scene.plugins.fbx.objects; + +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxCluster extends FbxObject { + + public int[] indexes; + public double[] weights; + public double[] transform; + public double[] transformLink; + public FbxSkin skin; + + public FbxCluster(SceneLoader scene, FbxElement element) { + super(scene, element); + for(FbxElement e : element.children) { + switch(e.id) { + case "Indexes": + indexes = (int[]) e.properties.get(0); + break; + case "Weights": + weights = (double[]) e.properties.get(0); + break; + case "Transform": + transform = (double[]) e.properties.get(0); + break; + case "TransformLink": + transformLink = (double[]) e.properties.get(0); + break; + } + } + } + + @Override + public void link(FbxObject child) { + if(child instanceof FbxNode) { + FbxNode limb = (FbxNode) child; + limb.skinToCluster.put(skin.id, this); + skin.bones.add(limb); + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java new file mode 100644 index 000000000..0b50aa163 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java @@ -0,0 +1,123 @@ +package com.jme3.scene.plugins.fbx.objects; + +import java.io.File; + +import com.jme3.asset.AssetManager; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; +import com.jme3.scene.plugins.fbx.ContentTextureKey; +import com.jme3.scene.plugins.fbx.ContentTextureLocator; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxImage extends FbxObject { + + String filename; + String relativeFilename; + byte[] content; + String imageType; + + public Image image; + + public FbxImage(SceneLoader scene, FbxElement element) { + super(scene, element); + if(type.equals("Clip")) { + for(FbxElement e : element.children) { + switch(e.id) { + case "Type": + imageType = (String) e.properties.get(0); + break; + case "Filename": + case "FileName": + filename = (String) e.properties.get(0); + break; + case "RelativeFilename": + relativeFilename = (String) e.properties.get(0); + break; + case "Content": + if(e.properties.size() > 0) + content = (byte[]) e.properties.get(0); + break; + } + } + image = createImage(); + } + } + + + private Image createImage() { + AssetManager assetManager = scene.assetManager; + Image image = null; + if(filename != null) { + // Try load by absolute path + File file = new File(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 && relativeFilename != null) { + // Try load by relative path + File dir = new File(scene.sceneFolderName); + String locatorPath = dir.getAbsolutePath(); + Texture tex = null; + try { + assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + tex = assetManager.loadTexture(relativeFilename); + } catch(Exception e) {} finally { + assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + } + if(tex != null) + image = tex.getImage(); + } + if(image == null && content != null) { + // Try load from content + String filename = null; + if(this.filename != null) + filename = new File(this.filename).getName(); + if(filename != null && this.relativeFilename != null) + filename = this.relativeFilename; + // Filename is required to aquire asset loader by extension + if(filename != null) { + String locatorPath = scene.sceneFilename; + filename = scene.sceneFilename + File.separatorChar + filename; // Unique path + Texture tex = null; + try { + assetManager.registerLocator(locatorPath, ContentTextureLocator.class); + tex = assetManager.loadTexture(new ContentTextureKey(filename, content)); + } catch(Exception e) {} finally { + assetManager.unregisterLocator(locatorPath, ContentTextureLocator.class); + } + if(tex != null) + image = tex.getImage(); + } + } + if(image == null) { + // Try to load from files near + if(relativeFilename != null) { + String[] split = relativeFilename.split("[\\\\/]"); + String filename = split[split.length - 1]; + Texture tex = null; + try { + tex = assetManager.loadTexture(new ContentTextureKey(scene.currentAssetInfo.getKey().getFolder() + filename, content)); + } catch(Exception e) {} + if(tex != null) + image = tex.getImage(); + } + } + if(image == null) + return new Image(Image.Format.RGB8, 1, 1, BufferUtils.createByteBuffer((int) ((long) 1 * (long) 1 * (long) Image.Format.RGB8.getBitsPerPixel() / 8L)), ColorSpace.Linear); + return image; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java new file mode 100644 index 000000000..c16d253ad --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java @@ -0,0 +1,113 @@ +package com.jme3.scene.plugins.fbx.objects; + +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxMaterial extends FbxObject { + + public String shadingModel = "phong"; + public Vector3f ambientColor = new Vector3f(0.2f, 0.2f, 0.2f); + public float ambientFactor = 1.0f; + public Vector3f diffuseColor = new Vector3f(0.8f, 0.8f, 0.8f); + public float diffuseFactor = 1.0f; + public Vector3f specularColor = new Vector3f(0.2f, 0.2f, 0.2f); + public float specularFactor = 1.0f; + public float shininessExponent = 1.0f; + + public Material material; + + public FbxMaterial(SceneLoader scene, FbxElement element) { + super(scene, element); + if(type.equals("")) { + for(FbxElement e : element.children) { + if(e.id.equals("ShadingModel")) { + shadingModel = (String) e.properties.get(0); + } else if(e.id.equals("Properties70")) { + for(FbxElement e2 : e.children) { + if(e2.id.equals("P")) { + double x, y, z; + String propName = (String) e2.properties.get(0); + switch(propName) { + case "AmbientColor": + x = (Double) e2.properties.get(4); + y = (Double) e2.properties.get(5); + z = (Double) e2.properties.get(6); + ambientColor.set((float) x, (float) y, (float) z); + break; + case "AmbientFactor": + x = (Double) e2.properties.get(4); + ambientFactor = (float) x; + break; + case "DiffuseColor": + x = (Double) e2.properties.get(4); + y = (Double) e2.properties.get(5); + z = (Double) e2.properties.get(6); + diffuseColor.set((float) x, (float) y, (float) z); + break; + case "DiffuseFactor": + x = (Double) e2.properties.get(4); + diffuseFactor = (float) x; + break; + case "SpecularColor": + x = (Double) e2.properties.get(4); + y = (Double) e2.properties.get(5); + z = (Double) e2.properties.get(6); + specularColor.set((float) x, (float) y, (float) z); + break; + case "Shininess": + case "ShininessExponent": + x = (Double) e2.properties.get(4); + shininessExponent = (float) x; + break; + } + } + } + } + } + material = createMaterial(); + } + } + + @Override + public void link(FbxObject otherObject, String propertyName) { + if(otherObject instanceof FbxTexture) { + FbxTexture tex = (FbxTexture) otherObject; + if(tex.texture == null || material == null) + return; + switch(propertyName) { + case "DiffuseColor": + material.setTexture("DiffuseMap", tex.texture); + material.setColor("Diffuse", ColorRGBA.White); + break; + case "SpecularColor": + material.setTexture("SpecularMap", tex.texture); + material.setColor("Specular", ColorRGBA.White); + break; + case "NormalMap": + material.setTexture("NormalMap", tex.texture); + break; + } + } + } + + private Material createMaterial() { + Material m = new Material(scene.assetManager, "Common/MatDefs/Light/Lighting.j3md"); + m.setName(name); + ambientColor.multLocal(ambientFactor); + diffuseColor.multLocal(diffuseFactor); + specularColor.multLocal(specularFactor); + m.setColor("Ambient", new ColorRGBA(ambientColor.x, ambientColor.y, ambientColor.z, 1)); + m.setColor("Diffuse", new ColorRGBA(diffuseColor.x, diffuseColor.y, diffuseColor.z, 1)); + m.setColor("Specular", new ColorRGBA(specularColor.x, specularColor.y, specularColor.z, 1)); + m.setFloat("Shininess", shininessExponent); + m.setBoolean("UseMaterialColors", true); + m.setFloat("AlphaDiscardThreshold", 0.5f); // TODO replace with right way in JME to set "Aplha Test" + m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + return m; + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java new file mode 100644 index 000000000..b73524b8b --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java @@ -0,0 +1,537 @@ +package com.jme3.scene.plugins.fbx.objects; + +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.jme3.asset.AssetLoadException; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.Mesh.Mode; +import com.jme3.scene.Node; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; + +public class FbxMesh extends FbxObject { + + public double[] vertices; + public int[] indices; + public int[] edges; + public String normalsMapping; + public String normalsReference; + public double[] normals; + public String tangentsMapping; + public String tangentsReference; + public double[] tangents; + public String binormalsMapping; + public String binormalsReference; + public double[] binormals; + public String uvMapping; + public String uvReference; + public double[] uv; + public int[] uvIndex; + public List uvIndexes = new ArrayList<>(); + public List uvs = new ArrayList<>(); + public String smoothingMapping; + public String smoothingReference; + public int[] smoothing; + public String materialsMapping; + public String materialsReference; + public int[] materials; + // Build helping data + public int iCount; + public int vCount; + public int srcVertexCount; + public List vertexMap; // Target vertex -> source vertex + public List> reverseVertexMap; // source vertex -> list of target vertices + public List indexMap; // Target vertex -> source index + + public List geometries; // One mesh can be split in two geometries in case of by-polygon material mapping + public FbxNode parent; + public int lastMaterialId = 0; + + public FbxMesh(SceneLoader scene, FbxElement element) throws IOException { + super(scene, element); + if(type.equals("Mesh")) { + data: for(FbxElement e : element.children) { + switch(e.id) { + case "Vertices": + vertices = (double[]) e.properties.get(0); + break; + case "PolygonVertexIndex": + indices = (int[]) e.properties.get(0); + break; + // TODO edges are not used now + /*case "Edges": + edges = (int[]) e.properties.get(0); + break;*/ + case "LayerElementNormal": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + normalsMapping = (String) e2.properties.get(0); + if(!normalsMapping.equals("ByVertice") && !normalsMapping.equals("ByPolygonVertex")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementNormal.MappingInformationType attribute (" + normalsReference + ")"); + continue data; + } + break; + case "ReferenceInformationType": + normalsReference = (String) e2.properties.get(0); + if(!normalsReference.equals("Direct")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementNormal.ReferenceInformationType attribute (" + normalsReference + ")"); + continue data; + } + break; + case "Normals": + normals = (double[]) e2.properties.get(0); + break; + } + } + break; + case "LayerElementTangent": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + tangentsMapping = (String) e2.properties.get(0); + if(!tangentsMapping.equals("ByVertice") && !tangentsMapping.equals("ByPolygonVertex")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementTangent.MappingInformationType attribute (" + tangentsMapping + ")"); + continue data; + } + break; + case "ReferenceInformationType": + tangentsReference = (String) e2.properties.get(0); + if(!tangentsReference.equals("Direct")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementTangent.ReferenceInformationType attribute (" + tangentsReference + ")"); + continue data; + } + break; + case "Tangents": + tangents = (double[]) e2.properties.get(0); + break; + } + } + break; + case "LayerElementBinormal": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + binormalsMapping = (String) e2.properties.get(0); + if(!binormalsMapping.equals("ByVertice") && !binormalsMapping.equals("ByPolygonVertex")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementBinormal.MappingInformationType attribute (" + binormalsMapping + ")"); + continue data; + } + break; + case "ReferenceInformationType": + binormalsReference = (String) e2.properties.get(0); + if(!binormalsReference.equals("Direct")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementBinormal.ReferenceInformationType attribute (" + binormalsReference + ")"); + continue data; + } + break; + case "Tangents": + binormals = (double[]) e2.properties.get(0); + break; + } + } + break; + case "LayerElementUV": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + uvMapping = (String) e2.properties.get(0); + if(!uvMapping.equals("ByPolygonVertex")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementUV.MappingInformationType attribute (" + uvMapping + ")"); + continue data; + } + break; + case "ReferenceInformationType": + uvReference = (String) e2.properties.get(0); + if(!uvReference.equals("IndexToDirect")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementUV.ReferenceInformationType attribute (" + uvReference + ")"); + continue data; + } + break; + case "UV": + uv = (double[]) e2.properties.get(0); + uvs.add(uv); + break; + case "UVIndex": + uvIndex = (int[]) e2.properties.get(0); + uvIndexes.add(uvIndex); + break; + } + } + break; + // TODO smoothing is not used now + /*case "LayerElementSmoothing": + for(FBXElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + smoothingMapping = (String) e2.properties.get(0); + if(!smoothingMapping.equals("ByEdge")) + throw new AssetLoadException("Not supported LayerElementSmoothing.MappingInformationType = " + smoothingMapping); + break; + case "ReferenceInformationType": + smoothingReference = (String) e2.properties.get(0); + if(!smoothingReference.equals("Direct")) + throw new AssetLoadException("Not supported LayerElementSmoothing.ReferenceInformationType = " + smoothingReference); + break; + case "Smoothing": + smoothing = (int[]) e2.properties.get(0); + break; + } + } + break;*/ + case "LayerElementMaterial": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + materialsMapping = (String) e2.properties.get(0); + if(!materialsMapping.equals("AllSame") && !materialsMapping.equals("ByPolygon")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementMaterial.MappingInformationType attribute (" + materialsMapping + ")"); + continue data; + } + break; + case "ReferenceInformationType": + materialsReference = (String) e2.properties.get(0); + if(!materialsReference.equals("IndexToDirect")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementMaterial.ReferenceInformationType attribute (" + materialsReference + ")"); + continue data; + } + break; + case "Materials": + materials = (int[]) e2.properties.get(0); + break; + } + } + break; + } + } + geometries = createGeometries(); + } + } + + public void setParent(Node node) { + for(int i = 0; i < geometries.size(); ++i) { + Geometry geom = geometries.get(i); + geom.setName(node.getName() + (i > 0 ? "-" + i : "")); + geom.updateModelBound(); + node.attachChild(geom); + } + } + + @Override + public void linkToZero() { + setParent(scene.sceneNode); + } + + public void clearMaterials() { + for(Geometry g : geometries) { + if(g.getUserData("FBXMaterial") != null) + g.setUserData("FBXMaterial", null); + } + } + + @Override + public void link(FbxObject otherObject) { + if(otherObject instanceof FbxSkin) { + FbxSkin skin = (FbxSkin) otherObject; + skin.toSkin.add(this); + } + } + + private List createGeometries() throws IOException { + 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) + if(indices != null) { + iCount = indices.length; + srcVertexCount = vertices.length / 3; + // Indices contains negative numbers to define polygon last index + // Check indices strides to be sure we have triangles or quads + vCount = 0; + // Count number of vertices to be produced + int polyVertCount = 0; + for(int i = 0; i < iCount; ++i) { + int index = indices[i]; + polyVertCount++; + if(index < 0) { + if(polyVertCount == 3) { + vCount += 3; // A triangle + } else if(polyVertCount == 4) { + vCount += 6; // A quad produce two triangles + } else { + throw new AssetLoadException("Unsupported PolygonVertexIndex stride"); + } + polyVertCount = 0; + } + } + // Unroll index array into vertex mapping + vertexMap = new ArrayList<>(vCount); + indexMap = new ArrayList<>(vCount); + polyVertCount = 0; + for(int i = 0; i < iCount; ++i) { + int index = indices[i]; + polyVertCount++; + if(index < 0) { + int lastIndex = -(index + 1); + if(polyVertCount == 3) { + vertexMap.add(indices[i - 2]); + vertexMap.add(indices[i - 1]); + vertexMap.add(lastIndex); + indexMap.add(i - 2); + indexMap.add(i - 1); + indexMap.add(i - 0); + } else if(polyVertCount == 4) { + vertexMap.add(indices[i - 3]); + vertexMap.add(indices[i - 2]); + vertexMap.add(indices[i - 1]); + vertexMap.add(indices[i - 3]); + vertexMap.add(indices[i - 1]); + vertexMap.add(lastIndex); + indexMap.add(i - 3); + indexMap.add(i - 2); + indexMap.add(i - 1); + indexMap.add(i - 3); + indexMap.add(i - 1); + indexMap.add(i - 0); + } + polyVertCount = 0; + } + } + // Build reverse vertex mapping + reverseVertexMap = new ArrayList<>(srcVertexCount); + for(int i = 0; i < srcVertexCount; ++i) + reverseVertexMap.add(new ArrayList<>()); + for(int i = 0; i < vCount; ++i) { + int index = vertexMap.get(i); + reverseVertexMap.get(index).add(i); + } + } else { + // Stub for no vertex indexing (direct mapping) + iCount = vCount = srcVertexCount; + vertexMap = new ArrayList<>(vCount); + indexMap = new ArrayList<>(vCount); + reverseVertexMap = new ArrayList<>(vCount); + for(int i = 0; i < vCount; ++i) { + vertexMap.set(i, i); + indexMap.set(i, i); + List l = new ArrayList(1); + l.add(i); + reverseVertexMap.add(l); + } + } + if(vertices != null) { + // Unroll vertices data array + FloatBuffer posBuf = BufferUtils.createFloatBuffer(vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf); + int srcCount = vertices.length / 3; + for(int i = 0; i < vCount; ++i) { + int index = vertexMap.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount); + float x = (float) vertices[3 * index + 0] / scene.unitSize * scene.xAxis; // XXX Why we should scale by unit size? + float y = (float) vertices[3 * index + 1] / scene.unitSize * scene.yAxis; + float z = (float) vertices[3 * index + 2] / scene.unitSize * scene.zAxis; + posBuf.put(x).put(y).put(z); + } + } + if(normals != null) { + // Unroll normals data array + FloatBuffer normBuf = BufferUtils.createFloatBuffer(vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf); + List mapping = null; + if(normalsMapping.equals("ByVertice")) + mapping = vertexMap; + else if(normalsMapping.equals("ByPolygonVertex")) + mapping = indexMap; + else + throw new IOException("Unknown normals mapping type: " + normalsMapping); + int srcCount = normals.length / 3; + for(int i = 0; i < vCount; ++i) { + int index = mapping.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount); + float x = (float) normals[3 * index + 0] * scene.xAxis; + float y = (float) normals[3 * index + 1] * scene.yAxis; + float z = (float) normals[3 * index + 2] * scene.zAxis; + normBuf.put(x).put(y).put(z); + } + } + if(tangents != null) { + // Unroll normals data array + FloatBuffer tanBuf = BufferUtils.createFloatBuffer(vCount * 4); + mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tanBuf); + List mapping = null; + if(tangentsMapping.equals("ByVertice")) + mapping = vertexMap; + else if(tangentsMapping.equals("ByPolygonVertex")) + mapping = indexMap; + else + throw new IOException("Unknown tangents mapping type: " + tangentsMapping); + int srcCount = tangents.length / 3; + for(int i = 0; i < vCount; ++i) { + int index = mapping.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount); + float x = (float) tangents[3 * index + 0] * scene.xAxis; + float y = (float) tangents[3 * index + 1] * scene.yAxis; + float z = (float) tangents[3 * index + 2] * scene.zAxis; + tanBuf.put(x).put(y).put(z).put(-1.0f); + } + } + if(binormals != null) { + // Unroll normals data array + FloatBuffer binormBuf = BufferUtils.createFloatBuffer(vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormBuf); + List mapping = null; + if(binormalsMapping.equals("ByVertice")) + mapping = vertexMap; + else if(binormalsMapping.equals("ByPolygonVertex")) + mapping = indexMap; + else + throw new IOException("Unknown binormals mapping type: " + binormalsMapping); + int srcCount = binormals.length / 3; + for(int i = 0; i < vCount; ++i) { + int index = mapping.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount); + float x = (float) binormals[3 * index + 0] * scene.xAxis; + float y = (float) binormals[3 * index + 1] * scene.yAxis; + float z = (float) binormals[3 * index + 2] * scene.zAxis; + binormBuf.put(x).put(y).put(z); + } + } + for(int uvLayer = 0; uvLayer < uvs.size(); ++uvLayer) { + double[] uv = uvs.get(uvLayer); + int[] uvIndex = uvIndexes.size() > uvLayer ? uvIndexes.get(uvLayer) : null; + List unIndexMap = vertexMap; + if(uvIndex != null) { + int uvIndexSrcCount = uvIndex.length; + if(uvIndexSrcCount != iCount) + throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + iCount); + // Unroll UV index array + unIndexMap = new ArrayList<>(vCount); + int polyVertCount = 0; + for(int i = 0; i < iCount; ++i) { + int index = indices[i]; + polyVertCount++; + if(index < 0) { + if(polyVertCount == 3) { + unIndexMap.add(uvIndex[i - 2]); + unIndexMap.add(uvIndex[i - 1]); + unIndexMap.add(uvIndex[i - 0]); + } else if(polyVertCount == 4) { + unIndexMap.add(uvIndex[i - 3]); + unIndexMap.add(uvIndex[i - 2]); + unIndexMap.add(uvIndex[i - 1]); + unIndexMap.add(uvIndex[i - 3]); + unIndexMap.add(uvIndex[i - 1]); + unIndexMap.add(uvIndex[i - 0]); + } + polyVertCount = 0; + } + } + } + // Unroll UV data array + FloatBuffer tcBuf = BufferUtils.createFloatBuffer(vCount * 2); + VertexBuffer.Type type = VertexBuffer.Type.TexCoord; + switch(uvLayer) { + case 1: + type = VertexBuffer.Type.TexCoord2; + break; + case 2: + type = VertexBuffer.Type.TexCoord3; + break; + case 3: + type = VertexBuffer.Type.TexCoord4; + break; + case 4: + type = VertexBuffer.Type.TexCoord5; + break; + case 5: + type = VertexBuffer.Type.TexCoord6; + break; + case 6: + type = VertexBuffer.Type.TexCoord7; + break; + case 7: + type = VertexBuffer.Type.TexCoord8; + break; + } + mesh.setBuffer(type, 2, tcBuf); + int srcCount = uv.length / 2; + for(int i = 0; i < vCount; ++i) { + int index = unIndexMap.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount); + float u = (index >= 0) ? (float) uv[2 * index + 0] : 0; + float v = (index >= 0) ? (float) uv[2 * index + 1] : 0; + tcBuf.put(u).put(v); + } + } + List geometries = new ArrayList(); + if(materialsReference.equals("IndexToDirect") && materialsMapping.equals("ByPolygon")) { + IntMap> indexBuffers = new IntMap<>(); + for(int polygon = 0; polygon < materials.length; ++polygon) { + int material = materials[polygon]; + List list = indexBuffers.get(material); + if(list == null) { + list = new ArrayList<>(); + indexBuffers.put(material, list); + } + list.add(polygon * 3 + 0); + list.add(polygon * 3 + 1); + list.add(polygon * 3 + 2); + } + Iterator>> iterator = indexBuffers.iterator(); + while(iterator.hasNext()) { + Entry> e = iterator.next(); + int materialId = e.getKey(); + List indexes = e.getValue(); + Mesh newMesh = mesh.clone(); + newMesh.setBuffer(VertexBuffer.Type.Index, 3, toArray(indexes.toArray(new Integer[indexes.size()]))); + newMesh.setStatic(); + newMesh.updateBound(); + newMesh.updateCounts(); + Geometry geom = new Geometry(); + geom.setMesh(newMesh); + geometries.add(geom); + geom.setUserData("FBXMaterial", materialId); + } + } else { + mesh.setStatic(); + mesh.updateBound(); + mesh.updateCounts(); + Geometry geom = new Geometry(); + geom.setMesh(mesh); + geometries.add(geom); + } + return geometries; + } + + private static int[] toArray(Integer[] arr) { + int[] ret = new int[arr.length]; + for(int i = 0; i < arr.length; ++i) + ret[i] = arr[i].intValue(); + return ret; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java new file mode 100644 index 000000000..b91d3d20e --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java @@ -0,0 +1,274 @@ +package com.jme3.scene.plugins.fbx.objects; + +import java.util.HashMap; +import java.util.Map; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.material.Material; +import com.jme3.material.RenderState.FaceCullMode; +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.Node; +import com.jme3.scene.plugins.fbx.RotationOrder; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxNode extends FbxObject { + + public Map userData = new HashMap<>(); + public FaceCullMode cullMode = FaceCullMode.Back; + public Transform localTransform; + public Node node; + public FbxNode parentFbxNode; + + public boolean rotationActive = false; + public RotationOrder rotationOrder = RotationOrder.EULER_XYZ; + + + // For bones and animation, in world space + public Matrix4f bindTransform = null; + public int boneIndex; + public Map animTranslations = new HashMap<>(); + public Map animRotations = new HashMap<>(); + public Map animScales = new HashMap<>(); + public Bone bone; + private FbxAnimNode lastAnimTranslation; + private FbxAnimNode lastAnimRotation; + private FbxAnimNode lastAnimScale; + private FbxMesh mesh; + public Map skinToCluster = new HashMap<>(); + + public FbxNode(SceneLoader scene, FbxElement element) { + super(scene, element); + Vector3f translationLocalRaw = new Vector3f(); + Vector3f rotationOffsetRaw = new Vector3f(); + Vector3f rotationPivotRaw = new Vector3f(); + Vector3f rotationPreRaw = new Vector3f(); + Vector3f rotationLocalRaw = new Vector3f(); + Vector3f rotationPostRaw = new Vector3f(); + Vector3f scaleOffsetRaw = new Vector3f(); + Vector3f scalePivotRaw = new Vector3f(); + Vector3f scaleLocalRaw = new Vector3f(1, 1, 1); + for(FbxElement prop : element.getFbxProperties()) { + double x, y, z; + String propName = (String) prop.properties.get(0); + switch(propName) { + case "RotationOrder": + rotationOrder = RotationOrder.values[(Integer) prop.properties.get(4)]; + break; + case "Lcl Translation": + readVectorFromProp(translationLocalRaw, prop); + break; + case "Lcl Rotation": + readVectorFromProp(rotationLocalRaw, prop); + break; + case "Lcl Scaling": + readVectorFromProp(scaleLocalRaw, prop); + break; + case "PreRotation": + readVectorFromProp(rotationPreRaw, prop); + break; + case "RotationActive": + rotationActive = ((Number) prop.properties.get(4)).intValue() == 1; + break; + case "RotationPivot": + readVectorFromProp(rotationPivotRaw, prop); + break; + case "PostRotation": + readVectorFromProp(rotationPostRaw, prop); + break; + case "ScaleOffset": + readVectorFromProp(scaleOffsetRaw, prop); + break; + case "ScalePivot": + readVectorFromProp(scalePivotRaw, prop); + break; + case "U": + String userDataKey = (String) prop.properties.get(0); + String userDataType = (String) prop.properties.get(1); + Object userDataValue; + if(userDataType.equals("KString")) { + userDataValue = (String) prop.properties.get(4); + } else if(userDataType.equals("int")) { + userDataValue = (Integer) prop.properties.get(4); + } else if(userDataType.equals("double")) { + // NOTE: jME3 does not support doubles in UserData. + // Need to convert to float. + userDataValue = ((Double) prop.properties.get(4)).floatValue(); + } else if(userDataType.equals("Vector")) { + x = (Double) prop.properties.get(4); + y = (Double) prop.properties.get(5); + z = (Double) prop.properties.get(6); + userDataValue = new Vector3f((float) x, (float) y, (float) z); + } else { + scene.warning("Unsupported user data type: " + userDataType + ". Ignoring."); + continue; + } + userData.put(userDataKey, userDataValue); + break; + } + } + + FbxElement cullingElement = element.getChildById("Culling"); + if(cullingElement != null && cullingElement.properties.get(0).equals("CullingOff")) + cullMode = FaceCullMode.Off; // TODO Add other variants + + /*From http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/the-makeup-of-the-local-matrix-of-an-kfbxnode/ + + Local Matrix = LclTranslation * RotationOffset * RotationPivot * + PreRotation * LclRotation * PostRotation * RotationPivotInverse * + ScalingOffset * ScalingPivot * LclScaling * ScalingPivotInverse + + LocalTranslation : translate (xform -query -translation) + RotationOffset: translation compensates for the change in the rotate pivot point (xform -q -rotateTranslation) + RotationPivot: current rotate pivot position (xform -q -rotatePivot) + PreRotation : joint orientation(pre rotation) + LocalRotation: rotate transform (xform -q -rotation & xform -q -rotateOrder) + PostRotation : rotate axis (xform -q -rotateAxis) + RotationPivotInverse: inverse of RotationPivot + ScalingOffset: translation compensates for the change in the scale pivot point (xform -q -scaleTranslation) + ScalingPivot: current scale pivot position (xform -q -scalePivot) + LocalScaling: scale transform (xform -q -scale) + ScalingPivotInverse: inverse of ScalingPivot + */ + + RotationOrder rotOrder = rotationActive ? rotationOrder : RotationOrder.EULER_XYZ; + + Matrix4f transformMatrix = new Matrix4f(); + transformMatrix.setTranslation(translationLocalRaw.x + rotationOffsetRaw.x + rotationPivotRaw.x, translationLocalRaw.y + rotationOffsetRaw.y + rotationPivotRaw.y, translationLocalRaw.z + rotationOffsetRaw.z + rotationPivotRaw.z); + + if(rotationActive) { + Quaternion postRotation = rotOrder.rotate(rotationPostRaw.x, rotationPostRaw.y, rotationPostRaw.z); + Quaternion localRotation = rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z); + Quaternion preRotation = rotOrder.rotate(rotationPreRaw.x, rotationPreRaw.y, rotationPreRaw.z); + //preRotation.multLocal(localRotation).multLocal(postRotation); + postRotation.multLocal(localRotation).multLocal(preRotation); + transformMatrix.multLocal(postRotation); + } else { + transformMatrix.multLocal(rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z)); + } + + Matrix4f mat = new Matrix4f(); + mat.setTranslation(scaleOffsetRaw.x + scalePivotRaw.x - rotationPivotRaw.x, scaleOffsetRaw.y + scalePivotRaw.y - rotationPivotRaw.y, scaleOffsetRaw.z + scalePivotRaw.z - rotationPivotRaw.z); + transformMatrix.multLocal(mat); + + transformMatrix.scale(scaleLocalRaw); + transformMatrix.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize)); + + mat.setTranslation(scalePivotRaw.negate()); + transformMatrix.multLocal(mat); + + localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector()); + + node = new Node(name); + userData.forEach((s, o) -> node.setUserData(s, o)); + node.setLocalTransform(localTransform); + } + + @Override + public void linkToZero() { + scene.sceneNode.attachChild(node); + } + + public void setSkeleton(Skeleton skeleton) { + if(bone != null) + boneIndex = skeleton.getBoneIndex(bone); + } + + public void buildBindPoseBoneTransform() { + if(bone != null) { + Matrix4f t = bindTransform; + if(t != null) { + Matrix4f parentMatrix = parentFbxNode != null ? parentFbxNode.bindTransform : Matrix4f.IDENTITY; + if(parentMatrix == null) + parentMatrix = node.getLocalToWorldMatrix(null); + t = parentMatrix.invert().multLocal(t); + bone.setBindTransforms(t.toTranslationVector(), t.toRotationQuat(), t.toScaleVector()); + } else { + bone.setBindTransforms(node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale()); + } + } + } + + @Override + public void link(FbxObject child, String propertyName) { + if(child instanceof FbxAnimNode) { + FbxAnimNode anim = (FbxAnimNode) child; + switch(propertyName) { + case "Lcl Translation": + animTranslations.put(anim.layerId, anim); + lastAnimTranslation = anim; + break; + case "Lcl Rotation": + animRotations.put(anim.layerId, anim); + lastAnimRotation = anim; + break; + case "Lcl Scaling": + animScales.put(anim.layerId, anim); + lastAnimScale = anim; + break; + } + } + } + + public FbxAnimNode animTranslation(long layerId) { + if(layerId == 0) + return lastAnimTranslation; + return animTranslations.get(layerId); + } + + public FbxAnimNode animRotation(long layerId) { + if(layerId == 0) + return lastAnimRotation; + return animRotations.get(layerId); + } + + public FbxAnimNode animScale(long layerId) { + if(layerId == 0) + return lastAnimScale; + return animScales.get(layerId); + } + + @Override + public void link(FbxObject otherObject) { + if(otherObject instanceof FbxMaterial) { + FbxMaterial m = (FbxMaterial) otherObject; + Material mat = m.material; + if(cullMode != FaceCullMode.Back) + mat.getAdditionalRenderState().setFaceCullMode(cullMode); + for(Geometry g : mesh.geometries) { + if(g.getUserData("FBXMaterial") != null) { + if((Integer) g.getUserData("FBXMaterial") == mesh.lastMaterialId) + g.setMaterial(mat); + } else { + g.setMaterial(mat); + } + } + mesh.lastMaterialId++; + } else if(otherObject instanceof FbxNode) { + FbxNode n = (FbxNode) otherObject; + node.attachChild(n.node); + n.parentFbxNode = this; + if(isLimb() && n.isLimb()) { + if(bone == null) + bone = new Bone(name); + if(n.bone == null) + n.bone = new Bone(n.name); + bone.addChild(n.bone); + } + } else if(otherObject instanceof FbxMesh) { + FbxMesh m = (FbxMesh) otherObject; + m.setParent(node); + m.parent = this; + mesh = m; + } + } + + public boolean isLimb() { + return type.equals("LimbNode"); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java new file mode 100644 index 000000000..57611f072 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java @@ -0,0 +1,40 @@ +package com.jme3.scene.plugins.fbx.objects; + +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxObject { + + protected final SceneLoader scene; + public final FbxElement element; + public final long id; + public final String name; + public final String type; + + public FbxObject(SceneLoader scene, FbxElement element) { + this.scene = scene; + this.element = element; + this.id = (Long) element.properties.get(0); + String name = (String) element.properties.get(1); + this.name = name.substring(0, name.indexOf(0)); + this.type = (String) element.properties.get(2); + } + + public void link(FbxObject child) { + } + + public void link(FbxObject child, String propertyName) { + } + + // Parent is 0 id + public void linkToZero() { + } + + protected static void readVectorFromProp(Vector3f store, FbxElement propElement) { + float x = ((Double) propElement.properties.get(4)).floatValue(); + float y = ((Double) propElement.properties.get(5)).floatValue(); + float z = ((Double) propElement.properties.get(6)).floatValue(); + store.set(x, y, z); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java new file mode 100644 index 000000000..896860077 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java @@ -0,0 +1,150 @@ +package com.jme3.scene.plugins.fbx.objects; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.jme3.asset.AssetLoadException; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.util.BufferUtils; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxSkin extends FbxObject { + + public String skinningType; + public List toSkin = new ArrayList<>(); + public List bones = new ArrayList<>(); + + public FbxSkin(SceneLoader scene, FbxElement element) { + super(scene, element); + for(FbxElement e : element.children) { + switch(e.id) { + case "SkinningType": + skinningType = (String) e.properties.get(0); + break; + } + } + } + + @Override + public void link(FbxObject otherObject) { + if(otherObject instanceof FbxCluster) { + FbxCluster cluster = ((FbxCluster) otherObject); + cluster.skin = this; + } + } + + public void generateSkinning() { + for(FbxMesh fbxMesh : toSkin) { + if(fbxMesh.geometries == null) + continue; + Mesh firstMesh = fbxMesh.geometries.get(0).getMesh(); + int maxWeightsPerVert = generateBoneData(firstMesh, fbxMesh); + for(int i = 0; i < fbxMesh.geometries.size(); ++i) { + Mesh mesh = fbxMesh.geometries.get(i).getMesh(); + if(mesh != firstMesh) { + mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneWeight)); + mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneIndex)); + mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneWeight)); + mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneIndex)); + } + mesh.setMaxNumWeights(maxWeightsPerVert); + mesh.generateBindPose(true); + } + } + } + + private int generateBoneData(Mesh mesh, FbxMesh fbxMesh) { + // Create bone buffers + FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(fbxMesh.vCount * 4); + ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(fbxMesh.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); + int bonesLimitExceeded = 0; + // Accumulate skin bones influence into mesh buffers + for(FbxNode limb : bones) { + FbxCluster cluster = limb.skinToCluster.get(id); + if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length) + continue; + 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 >= fbxMesh.reverseVertexMap.size()) + throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + fbxMesh.reverseVertexMap.size()); + List dstVertices = fbxMesh.reverseVertexMap.get(vertexIndex); + for(int j = 0; j < dstVertices.size(); ++j) { + int v = dstVertices.get(j); + // Append bone index and weight to vertex + int offset; + int smalestOffset = 0; + float w = 0; + float smalestW = Float.MAX_VALUE; + for(offset = v * 4; offset < v * 4 + 4; ++offset) { + w = boneWeightData.get(offset); + if(w == 0) + break; + if(w < smalestW) { + smalestW = w; + smalestOffset = offset; + } + } + if(w == 0) { + boneWeightData.put(offset, (float) cluster.weights[i]); + boneIndicesData.put(offset, (byte) limb.boneIndex); + } else { + if((float) cluster.weights[i] > smalestW) { // If current weight more than smallest, discard smallest + boneWeightData.put(smalestOffset, (float) cluster.weights[i]); + boneIndicesData.put(smalestOffset, (byte) limb.boneIndex); + } + bonesLimitExceeded++; + } + } + } + } + if(bonesLimitExceeded > 0) + scene.warning("Skinning support max 4 bone per vertex. Exceeding data of " + bonesLimitExceeded + " weights in mesh bones will be discarded"); + // Postprocess bones weights + int maxWeightsPerVert = 0; + boneWeightData.rewind(); + for(int v = 0; v < fbxMesh.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); + } + } + return maxWeightsPerVert; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java new file mode 100644 index 000000000..6093d7228 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java @@ -0,0 +1,42 @@ +package com.jme3.scene.plugins.fbx.objects; + +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxTexture extends FbxObject { + + String bindType; + String filename; + + public Texture texture; + + public FbxTexture(SceneLoader scene, FbxElement element) { + super(scene, element); + for(FbxElement e : element.children) { + switch(e.id) { + case "Type": + bindType = (String) e.properties.get(0); + break; + case "FileName": + filename = (String) e.properties.get(0); + break; + } + } + texture = new Texture2D(); + texture.setName(name); + texture.setWrap(WrapMode.Repeat); // Default FBX wrapping. TODO: Investigate where this is stored (probably, in material) + } + + @Override + public void link(FbxObject otherObject) { + if(otherObject instanceof FbxImage) { + FbxImage img = (FbxImage) otherObject; + if(img.image == null) + return; + texture.setImage(img.image); + } + } +} From 4b6be2a78988207ff0c046d5e7c356cb0dea9ecb Mon Sep 17 00:00:00 2001 From: Eirenliel Allier Date: Tue, 26 Apr 2016 10:16:03 +0300 Subject: [PATCH 2/4] FBX Importer cleanup --- .../jme3/scene/plugins/fbx/RotationOrder.java | 56 +------------------ .../jme3/scene/plugins/fbx/SceneLoader.java | 5 +- 2 files changed, 5 insertions(+), 56 deletions(-) diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java index 94940609f..8a1e05b36 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java @@ -4,11 +4,11 @@ import com.jme3.math.FastMath; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; -// TODO This class has some potential in it... Should investigate public enum RotationOrder { EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ; + // Static values field for fast access by an oridinal without Enum.values() overhead public static final RotationOrder[] values = values(); private RotationOrder() { @@ -37,63 +37,13 @@ public enum RotationOrder { case EULER_XZY: return toQuat(x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y); case SPHERIC_XYZ: + default: throw new IllegalArgumentException("Spheric rotation is unsupported in this importer"); } - /*float c1 = FastMath.cos( x / 2 ); - float c2 = FastMath.cos( y / 2 ); - float c3 = FastMath.cos( z / 2 ); - float s1 = FastMath.sin( x / 2 ); - float s2 = FastMath.sin( y / 2 ); - float s3 = FastMath.sin( z / 2 ); - - float _x; - float _y; - float _z; - float _w; - - switch(order) { - case EULER_XYZ: - _x = s1 * c2 * c3 + c1 * s2 * s3; - _y = c1 * s2 * c3 - s1 * c2 * s3; - _z = c1 * c2 * s3 + s1 * s2 * c3; - _w = c1 * c2 * c3 - s1 * s2 * s3; - return new Quaternion(_x, _y, _z, _w); - case EULER_YXZ: - _x = s1 * c2 * c3 + c1 * s2 * s3; - _y = c1 * s2 * c3 - s1 * c2 * s3; - _z = c1 * c2 * s3 - s1 * s2 * c3; - _w = c1 * c2 * c3 + s1 * s2 * s3; - return new Quaternion(_x, _y, _z, _w); - case EULER_ZXY: - _x = s1 * c2 * c3 - c1 * s2 * s3; - _y = c1 * s2 * c3 + s1 * c2 * s3; - _z = c1 * c2 * s3 + s1 * s2 * c3; - _w = c1 * c2 * c3 - s1 * s2 * s3; - return new Quaternion(_x, _y, _z, _w); - case EULER_ZYX: - _x = s1 * c2 * c3 - c1 * s2 * s3; - _y = c1 * s2 * c3 + s1 * c2 * s3; - _z = c1 * c2 * s3 - s1 * s2 * c3; - _w = c1 * c2 * c3 + s1 * s2 * s3; - return new Quaternion(_x, _y, _z, _w); - case EULER_YZX: - _x = s1 * c2 * c3 + c1 * s2 * s3; - _y = c1 * s2 * c3 + s1 * c2 * s3; - _z = c1 * c2 * s3 - s1 * s2 * c3; - _w = c1 * c2 * c3 - s1 * s2 * s3; - return new Quaternion(_x, _y, _z, _w); - case EULER_XZY: - _x = s1 * c2 * c3 - c1 * s2 * s3; - _y = c1 * s2 * c3 - s1 * c2 * s3; - _z = c1 * c2 * s3 + s1 * s2 * c3; - _w = c1 * c2 * c3 + s1 * s2 * s3; - return new Quaternion(_x, _y, _z, _w); - } - */ - throw new AssertionError("Impossible"); } private static Quaternion toQuat(float ax1v, Vector3f ax1, float ax2v, Vector3f ax2, float ax3v, Vector3f ax3) { + // TODO It has some potential in optimization Quaternion q1 = new Quaternion().fromAngleNormalAxis(ax1v, ax1); Quaternion q2 = new Quaternion().fromAngleNormalAxis(ax2v, ax2); Quaternion q3 = new Quaternion().fromAngleNormalAxis(ax3v, ax3); 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 index cf4af7682..7b9a5a7f5 100644 --- 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 @@ -44,9 +44,8 @@ import com.jme3.scene.plugins.fbx.objects.FbxTexture; /** * 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 + * Multiple animations can be defined with {@link AnimationList} passing into {@link SceneKey} + * or loaded from different animation layer.

*/ public class SceneLoader implements AssetLoader { From 794932104beddd133e93053f80c6d00cdf6b75eb Mon Sep 17 00:00:00 2001 From: Eirenliel Allier Date: Tue, 26 Apr 2016 11:12:37 +0300 Subject: [PATCH 3/4] Fix compatibility with Java 7 --- .../java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java | 2 +- .../java/com/jme3/scene/plugins/fbx/objects/FbxNode.java | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java index b73524b8b..930dd830d 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java @@ -320,7 +320,7 @@ public class FbxMesh extends FbxObject { // Build reverse vertex mapping reverseVertexMap = new ArrayList<>(srcVertexCount); for(int i = 0; i < srcVertexCount; ++i) - reverseVertexMap.add(new ArrayList<>()); + reverseVertexMap.add(new ArrayList()); for(int i = 0; i < vCount; ++i) { int index = vertexMap.get(i); reverseVertexMap.get(index).add(i); diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java index b91d3d20e..ebb209492 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java @@ -19,7 +19,6 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxNode extends FbxObject { - public Map userData = new HashMap<>(); public FaceCullMode cullMode = FaceCullMode.Back; public Transform localTransform; public Node node; @@ -44,6 +43,7 @@ public class FbxNode extends FbxObject { public FbxNode(SceneLoader scene, FbxElement element) { super(scene, element); + node = new Node(name); Vector3f translationLocalRaw = new Vector3f(); Vector3f rotationOffsetRaw = new Vector3f(); Vector3f rotationPivotRaw = new Vector3f(); @@ -108,7 +108,7 @@ public class FbxNode extends FbxObject { scene.warning("Unsupported user data type: " + userDataType + ". Ignoring."); continue; } - userData.put(userDataKey, userDataValue); + node.setUserData(userDataKey, userDataValue); break; } } @@ -164,8 +164,6 @@ public class FbxNode extends FbxObject { localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector()); - node = new Node(name); - userData.forEach((s, o) -> node.setUserData(s, o)); node.setLocalTransform(localTransform); } From 681b2101dce29061f6c8588d23d125218f943a32 Mon Sep 17 00:00:00 2001 From: Eirenliel Allier Date: Tue, 26 Apr 2016 11:21:34 +0300 Subject: [PATCH 4/4] Fix building with JRE 1.7 --- .../jme3/scene/plugins/fbx/SceneLoader.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) 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 index 7b9a5a7f5..accbdbbf4 100644 --- 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 @@ -104,7 +104,7 @@ public class SceneLoader implements AssetLoader { loadScene(stream); linkScene(); if(warnings.size() > 0) - logger.log(Level.WARNING, "Model load finished with warnings:\n" + String.join("\n", warnings)); + logger.log(Level.WARNING, "Model load finished with warnings:\n" + join(warnings, "\n")); } finally { releaseObjects(); if(stream != null) @@ -430,10 +430,6 @@ public class SceneLoader implements AssetLoader { animControl.setAnimations(anims); } - private static boolean haveAnyChannel(FbxAnimNode anims) { - return anims != null && anims.haveAnyChannel(); - } - private void releaseObjects() { // Reset settings unitSize = 1; @@ -461,4 +457,19 @@ public class SceneLoader implements AssetLoader { animControl = null; sceneNode = null; } + + + private static boolean haveAnyChannel(FbxAnimNode anims) { + return anims != null && anims.haveAnyChannel(); + } + + private static String join(List list, String glue) { + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < list.size(); ++i) { + if(sb.length() != 0) + sb.append(glue); + sb.append(list.get(i)); + } + return sb.toString(); + } }