From 51d14ae869a6954297b34156ff2dd942a8bc6f33 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 6 Apr 2016 12:47:18 +0200 Subject: [PATCH] refactored switch statements and moved code blokcs to seperate functions added tests for UVCoordinatesGenerator now with the actual test file moved math utility functions to proper classes, added tests for the functions that were moved added tests for functions that were moved fixed indentation, added comments added tests to gradle for plugins wrote tests for retrieving subclassname, moved code to proper place created configuration file, refactored and wrote tests for dispatching FbxElements to FbxObjects removed outcommented code removed repositories that are not used anymore fixed wrongly initialized loggers added method comments fixed wrong description Added more UVCoordinatesGenerator tests reverted back to v2.0-beta of mockito removed unused import and restructured import list removed redundant variable took out material loading from sceneloader, added tests for material to object linking more tests for SceneLoader concerning textures extracted FbxTextureLoader, added tests for image/texture link removed incorrect gradle file deduced interface for two loader classes cleaned up gradle file --- common.gradle | 4 +- .../textures/UVCoordinatesGenerator.java | 212 +- .../textures/UVProjectionGenerator.java | 1 + .../textures/UVCoordinatesGeneratorTest.java | 250 ++ .../main/java/com/jme3/math/ColorRGBA.java | 18 + .../src/main/java/com/jme3/math/Vector2f.java | 16 + .../src/main/java/com/jme3/math/Vector3f.java | 16 + .../java/com/jme3/math/ColorRGBATest.java | 83 + .../test/java/com/jme3/math/Vector2fTest.java | 78 + .../test/java/com/jme3/math/Vector3fTest.java | 78 + jme3-plugins/build.gradle | 5 + .../jme3/scene/plugins/fbx/SceneLoader.java | 2841 ++++++++--------- .../scene/plugins/fbx/file/FbxElement.java | 38 +- .../plugins/fbx/loaders/FbxElementLoader.java | 22 + .../fbx/loaders/FbxMaterialLoader.java | 184 ++ .../plugins/fbx/loaders/FbxTextureLoader.java | 78 + .../plugins/fbx/loaders/PropertyLink.java | 19 + .../plugins/fbx/mesh/FbxLayerElement.java | 44 +- .../jme3/scene/plugins/fbx/mesh/FbxMesh.java | 2 +- .../fbx/misc/FbxClassTypeDispatcher.java | 113 + .../plugins/fbx/obj/FbxObjectFactory.java | 120 +- .../src/fbx/resources/FbxImportRules.json | 50 + .../scene/plugins/fbx/SceneLoaderTest.java | 386 +++ .../plugins/fbx/file/FbxElementTest.java | 40 + .../plugins/fbx/obj/FbxObjectFactoryTest.java | 413 +++ 25 files changed, 3359 insertions(+), 1752 deletions(-) create mode 100644 jme3-blender/src/test/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGeneratorTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/ColorRGBATest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/Vector2fTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/Vector3fTest.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxElementLoader.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxMaterialLoader.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxTextureLoader.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/PropertyLink.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxClassTypeDispatcher.java create mode 100644 jme3-plugins/src/fbx/resources/FbxImportRules.json create mode 100644 jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/SceneLoaderTest.java create mode 100644 jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/file/FbxElementTest.java create mode 100644 jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactoryTest.java diff --git a/common.gradle b/common.gradle index 2cad48bf22..36c1592423 100644 --- a/common.gradle +++ b/common.gradle @@ -25,9 +25,11 @@ configurations { dependencies { // Adding dependencies here will add the dependencies to each subproject. testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.mockito', name: 'mockito-core', version: '2.0.28-beta' testCompile group: 'org.easytesting', name: 'fest-assert-core', version: '2.0M10' + testCompile group: 'org.mockito', name: 'mockito-all', version: '1.9.5' + testCompile "org.powermock:powermock-mockito-release-full:1.6.1" deployerJars "org.apache.maven.wagon:wagon-ssh:2.9" + compile group: 'com.google.code.gson', name: 'gson', version: '2.2.4' } jar { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java index a922b2588f..fa9a373f02 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java @@ -95,62 +95,76 @@ public static List generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoor BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); float[] inputData = null;// positions, normals, reflection vectors, etc. - switch (texco) { - case TEXCO_ORCO: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); - break; - case TEXCO_UV:// this should be used if not defined by user explicitly - Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; - for (int i = 0; i < mesh.getVertexCount(); ++i) { - result.add(data[i % 3]); - } - break; - case TEXCO_NORM: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); - break; - case TEXCO_REFL: - case TEXCO_GLOB: - case TEXCO_TANGENT: - case TEXCO_STRESS: - case TEXCO_LAVECTOR: - case TEXCO_OBJECT: - case TEXCO_OSA: - case TEXCO_PARTICLE_OR_STRAND: - case TEXCO_SPEED: - case TEXCO_STICKY: - case TEXCO_VIEW: - case TEXCO_WINDOW: - LOGGER.warning("Texture coordinates type not currently supported: " + texco); - break; - default: - throw new IllegalStateException("Unknown texture coordinates value: " + texco); + //Check whether texco is supported, to reduce the switch statements needed + boolean texcoSupported = isTextureCoordinateTypeSupported(texco); + + if (texcoSupported) { + //Only if texco is supported, there will be additional calculations done + switch (texco) { + case TEXCO_ORCO: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); + break; + case TEXCO_UV:// this should be used if not defined by user explicitly + Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; + for (int i = 0; i < mesh.getVertexCount(); ++i) { + result.add(data[i % 3]); + } + break; + case TEXCO_NORM: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); + break; + default: + throw new IllegalStateException("Unknown texture coordinates value: " + texco); + } + } else { + LOGGER.warning("Texture coordinates type not currently supported: " + texco); } if (inputData != null) {// make projection calculations - switch (projection) { - case PROJECTION_FLAT: - inputData = UVProjectionGenerator.flatProjection(inputData, bb); - break; - case PROJECTION_CUBE: - inputData = UVProjectionGenerator.cubeProjection(inputData, bb); - break; - case PROJECTION_TUBE: - BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); - inputData = UVProjectionGenerator.tubeProjection(inputData, bt); - break; - case PROJECTION_SPHERE: - BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); - inputData = UVProjectionGenerator.sphereProjection(inputData, bs); - break; - default: - throw new IllegalStateException("Unknown projection type: " + projection); - } - for (int i = 0; i < inputData.length; i += 2) { - result.add(new Vector2f(inputData[i], inputData[i + 1])); - } + result = projectionCalculations(inputData, projection, bb, geometries); } return result; } + + /** + * Makes the projection calculations on the given inputData + * + * @param inputData + * the inputdata of the mesh (positions, normals, etc) + * @param projection + * projection type + * @param bb + * the bounding box + * @param geometries + * the geometris the given mesh belongs to (required to compute + * bounding box, sphere or tube) + * @return List of vector2f with projection calculations applied to it + */ + private static List projectionCalculations(float[] inputData, UVProjectionType projection, BoundingBox bb, Geometry geometries) { + List result = new ArrayList(); + switch (projection) { + case PROJECTION_FLAT: + inputData = UVProjectionGenerator.flatProjection(inputData, bb); + break; + case PROJECTION_CUBE: + inputData = UVProjectionGenerator.cubeProjection(inputData, bb); + break; + case PROJECTION_TUBE: + BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); + inputData = UVProjectionGenerator.tubeProjection(inputData, bt); + break; + case PROJECTION_SPHERE: + BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); + inputData = UVProjectionGenerator.sphereProjection(inputData, bs); + break; + default: + throw new IllegalStateException("Unknown projection type: " + projection); + } + for (int i = 0; i < inputData.length; i += 2) { + result.add(new Vector2f(inputData[i], inputData[i + 1])); + } + return result; + } /** * Generates a UV coordinates for 3D texture. @@ -170,57 +184,63 @@ public static List generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoor List result = new ArrayList(); BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); float[] inputData = null;// positions, normals, reflection vectors, etc. - - switch (texco) { - case TEXCO_ORCO: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); - break; - case TEXCO_UV: - Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; - for (int i = 0; i < mesh.getVertexCount(); ++i) { - Vector2f uv = data[i % 3]; - result.add(new Vector3f(uv.x, uv.y, 0)); - } - break; - case TEXCO_NORM: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); - break; - case TEXCO_REFL: - case TEXCO_GLOB: - case TEXCO_TANGENT: - case TEXCO_STRESS: - case TEXCO_LAVECTOR: - case TEXCO_OBJECT: - case TEXCO_OSA: - case TEXCO_PARTICLE_OR_STRAND: - case TEXCO_SPEED: - case TEXCO_STICKY: - case TEXCO_VIEW: - case TEXCO_WINDOW: - LOGGER.warning("Texture coordinates type not currently supported: " + texco); - break; - default: - throw new IllegalStateException("Unknown texture coordinates value: " + texco); + + //Check whether texco is supported, to reduce the switch statements needed + boolean texcoSupported = isTextureCoordinateTypeSupported(texco); + + if (texcoSupported) { + //Only if texco is supported, there will be additional calculations done + switch (texco) { + case TEXCO_ORCO: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); + break; + case TEXCO_UV: + Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; + for (int i = 0; i < mesh.getVertexCount(); ++i) { + Vector2f uv = data[i % 3]; + result.add(new Vector3f(uv.x, uv.y, 0)); + } + break; + case TEXCO_NORM: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); + break; + default: + throw new IllegalStateException("Unknown texture coordinates value: " + texco); + } } if (inputData != null) {// make calculations - Vector3f min = bb.getMin(null); - float[] uvCoordsResults = new float[4];// used for coordinates swapping - float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; - for (int i = 0; i < ext.length; ++i) { - if (ext[i] == 0) { - ext[i] = 1; - } - } - // now transform the coordinates so that they are in the range of - // <0; 1> - for (int i = 0; i < inputData.length; i += 3) { - uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; - uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; - uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; - result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]])); + result = coordinateCalculation(inputData, bb, coordinatesSwappingIndexes); + } + return result; + } + + /** + * Makes coordinate calculations + * + * @param inputData + * @param bb + * @param coordinatesSwappingIndexes + * @return + */ + private static List coordinateCalculation(float[] inputData, BoundingBox bb, int[] coordinatesSwappingIndexes) { + List result = new ArrayList(); + Vector3f min = bb.getMin(null); + float[] uvCoordsResults = new float[4];// used for coordinates swapping + float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; + for (int i = 0; i < ext.length; ++i) { + if (ext[i] == 0) { + ext[i] = 1; } } + // now transform the coordinates so that they are in the range of + // <0; 1> + for (int i = 0; i < inputData.length; i += 3) { + uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; + uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; + uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; + result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]])); + } return result; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java index a213bc6266..d0e3524a72 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java @@ -67,6 +67,7 @@ public static float[] flatProjection(float[] positions, BoundingBox bb) { * @return UV coordinates after the projection */ public static float[] cubeProjection(float[] positions, BoundingBox bb) { + Triangle triangle = new Triangle(); Vector3f x = new Vector3f(1, 0, 0); Vector3f y = new Vector3f(0, 1, 0); diff --git a/jme3-blender/src/test/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGeneratorTest.java b/jme3-blender/src/test/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGeneratorTest.java new file mode 100644 index 0000000000..86fd386ee7 --- /dev/null +++ b/jme3-blender/src/test/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGeneratorTest.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2009-2016 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.blender.textures; + +import com.jme3.bounding.BoundingBox; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; +import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +public class UVCoordinatesGeneratorTest { + + @Mock + private Mesh mockedMesh; + @Mock + private Geometry mockedGeometry; + + private BoundingBox bb; + private UVCoordinatesType texco; + private UVProjectionType projection; + private int[] indices; + + @Before + public void setup() { + mockedMesh = mock(Mesh.class); + projection = UVProjectionType.PROJECTION_CUBE; + bb = new BoundingBox(new Vector3f(1f, 2f ,3f), 1f, 2f, 3f); + + mockedGeometry = mock(Geometry.class); + doNothing().when(mockedGeometry).updateModelBound(); + when(mockedGeometry.getModelBound()).thenReturn(bb); + + indices = new int[0]; + } + + @Test + public void testUnsupportedTextureType2D() { + List result = null; + texco = UVCoordinatesType.TEXCO_LAVECTOR; + + result = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mockedMesh, texco, projection, mockedGeometry); + assertEquals(new ArrayList(), result); + } + + @Test + public void testORCOTextureType2D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_ORCO; + + initFloatBuffer(); + + result = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mockedMesh, texco, projection, mockedGeometry); + + ArrayList expected = new ArrayList(); + expected.add(new Vector2f(0.5f, 0.5f)); + expected.add(new Vector2f(2.0f, 1.25f)); + expected.add(new Vector2f(3.5f, 2.0f)); + + assertEquals(expected, result); + } + + @Test + public void testORCOTextureTypeFlatProjectionType2D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_ORCO; + projection = UVProjectionType.PROJECTION_FLAT; + + initFloatBuffer(); + + result = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mockedMesh, texco, projection, mockedGeometry); + + ArrayList expected = new ArrayList(); + expected.add(new Vector2f(0.5f, 0.5f)); + expected.add(new Vector2f(2.0f, 1.0f)); + expected.add(new Vector2f(3.5f, 1.5f)); + + assertEquals(expected, result); + } + + @Test + public void testORCOTextureTypeTubeProjectionType2D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_ORCO; + projection = UVProjectionType.PROJECTION_TUBE; + initFloatBuffer(); + + result = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mockedMesh, texco, projection, mockedGeometry); + + ArrayList expected = new ArrayList(); + expected.add(new Vector2f(0.25f, 0.5f)); + expected.add(new Vector2f(0.375f, 1.0f)); + expected.add(new Vector2f(0.375f, 1.5f)); + + assertEquals(expected, result); + } + + @Test + public void testORCOTextureTypeSphereProjectionType2D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_ORCO; + projection = UVProjectionType.PROJECTION_SPHERE; + + initFloatBuffer(); + + result = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mockedMesh, texco, projection, mockedGeometry); + + ArrayList expected = new ArrayList(); + expected.add(new Vector2f(0.25f, 0.5f)); + expected.add(new Vector2f(0.375f, 0.69591326f)); + expected.add(new Vector2f(0.375f, 0.69591326f)); + + assertEquals(expected, result); + } + + @Test + public void testUVTextureType2D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_UV; + + when(mockedMesh.getVertexCount()).thenReturn(2); + + result = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mockedMesh, texco, projection, mockedGeometry); + ArrayList expected = new ArrayList(); + expected.add(new Vector2f(0f, 1f)); + expected.add(new Vector2f(0f, 0f)); + + assertEquals(expected, result); + } + + @Test + public void testNORMTextureType2D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_NORM; + + result = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mockedMesh, texco, projection, mockedGeometry); + assertEquals(new ArrayList(), result); + } + + @Test + public void testUnsupportedTextureType3D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_SPEED; + + result = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mockedMesh, texco, indices, mockedGeometry); + assertEquals(new ArrayList(), result); + } + + @Test + public void testORCOTextureType3D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_ORCO; + indices = new int[3]; + indices[0] = 1; + indices[1] = 2; + indices[2] = 0; + + FloatBuffer buffer = FloatBuffer.allocate(9); + float[] floatsForBuffer = {1f,2f,3f,4f,5f,6f,7f,8f,9f}; + buffer.put(floatsForBuffer); + when(mockedMesh.getFloatBuffer(any(VertexBuffer.Type.class))).thenReturn(buffer); + + result = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mockedMesh, texco, indices, mockedGeometry); + + ArrayList expected = new ArrayList(); + expected.add(new Vector3f(0.5f, 0.5f, 0f)); + expected.add(new Vector3f(2.0f, 1.25f, 0f)); + expected.add(new Vector3f(3.5f, 2.0f, 0f)); + + assertEquals(expected, result); + } + + @Test + public void testUVTextureType3D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_UV; + + when(mockedMesh.getVertexCount()).thenReturn(2); + + result = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mockedMesh, texco, indices, mockedGeometry); + + ArrayList expected = new ArrayList(); + expected.add(new Vector3f(0f, 1f, 0f)); + expected.add(new Vector3f(0f, 0f, 0f)); + + assertEquals(expected, result); + } + + @Test + public void testNORMTextureType3D() { + List result = null; + UVCoordinatesType texco = UVCoordinatesType.TEXCO_NORM; + + result = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mockedMesh, texco, indices, mockedGeometry); + assertEquals(new ArrayList(), result); + } + + /** + * Helper function to create and prepare a FloatBuffer for the tests + */ + public void initFloatBuffer() { + FloatBuffer buffer = FloatBuffer.allocate(9); + float[] floatsForBuffer = {1f,2f,3f,4f,5f,6f,7f,8f,9f}; + buffer.put(floatsForBuffer); + when(mockedMesh.getFloatBuffer(any(VertexBuffer.Type.class))).thenReturn(buffer); + } +} diff --git a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java index 89df1bb2b9..f4761d897e 100644 --- a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java +++ b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java @@ -632,4 +632,22 @@ public ColorRGBA getAsSrgb() { return srgb; } + /** + * Gets the colors that are stored in the given data array. + * @param data an array of doubles. The amount of entries should be divisable by 4. + * @return an array of ColorRGBA objects + */ + public static ColorRGBA[] toColorRGBA(double[] data) { + assert data.length % 4 == 0; + ColorRGBA[] colors = new ColorRGBA[data.length / 4]; + for (int i = 0; i < colors.length; i++) { + float r = (float) data[i * 4]; + float g = (float) data[i * 4 + 1]; + float b = (float) data[i * 4 + 2]; + float a = (float) data[i * 4 + 3]; + colors[i] = new ColorRGBA(r, g, b, a); + } + return colors; + } + } diff --git a/jme3-core/src/main/java/com/jme3/math/Vector2f.java b/jme3-core/src/main/java/com/jme3/math/Vector2f.java index c1c2bcba49..e4729ae2b5 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector2f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector2f.java @@ -753,4 +753,20 @@ public void rotateAroundOrigin(float angle, boolean cw) { x = newX; y = newY; } + + /** + * Gets the Vector2f's that are stored in the given data array. + * @param data an array of doubles. The amount of entries should be divisable by 2. + * @return an array of Vector2f objects + */ + public static Vector2f[] toVector2(double[] data) { + assert data.length % 2 == 0; + Vector2f[] vectors = new Vector2f[data.length / 2]; + for (int i = 0; i < vectors.length; i++) { + float x = (float) data[i * 2]; + float y = (float) data[i * 2 + 1]; + vectors[i] = new Vector2f(x, y); + } + return vectors; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/Vector3f.java b/jme3-core/src/main/java/com/jme3/math/Vector3f.java index b5d2d8fc6e..2735ec56b4 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector3f.java @@ -1079,4 +1079,20 @@ public void set(int index, float value) { throw new IllegalArgumentException("index must be either 0, 1 or 2"); } + /** + * Gets the Vector3f's that are stored in the given data array. + * @param data an array of doubles. The amount of entries should be divisable by 3. + * @return an array of Vector3f objects + */ + public static Vector3f[] toVector3(double[] data) { + assert data.length % 3 == 0; + Vector3f[] vectors = new Vector3f[data.length / 3]; + for (int i = 0; i < vectors.length; i++) { + float x = (float) data[i * 3]; + float y = (float) data[i * 3 + 1]; + float z = (float) data[i * 3 + 2]; + vectors[i] = new Vector3f(x, y, z); + } + return vectors; + } } diff --git a/jme3-core/src/test/java/com/jme3/math/ColorRGBATest.java b/jme3-core/src/test/java/com/jme3/math/ColorRGBATest.java new file mode 100644 index 0000000000..2f1ef34d1b --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/ColorRGBATest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009-2016 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.math; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class ColorRGBATest { + + @Test + public void testEmptyArray() { + double[] data = new double[0]; + ColorRGBA[] colorArr = ColorRGBA.toColorRGBA(data); + assertEquals(0, colorArr.length); + } + + @Test(expected=AssertionError.class) + public void testArrayWithWrongLengthElements() { + double[] data = {1.0, 3.0, 1.5}; + ColorRGBA[] colorArr = ColorRGBA.toColorRGBA(data); + } + + @Test + public void testCorrectWorkingOneColor() { + double[] data = {0.1, 0.2, 0.3, 0.4}; + ColorRGBA[] colorArr = ColorRGBA.toColorRGBA(data); + assertEquals(1, colorArr.length); + ColorRGBA res = colorArr[0]; + double delta = 0.000001; + assertEquals(data[0], res.r, delta); + assertEquals(data[1], res.g, delta); + assertEquals(data[2], res.b, delta); + assertEquals(data[3], res.a, delta); + } + + @Test + public void testCorrectWorkingMultipleColors() { + double[] data = { + 0.1, 0.2, 0.3, 0.4, + 0.5, 0.6, 0.7, 0.8, + 0.9, 1.0, 1.1, 1.2, + }; + ColorRGBA[] colorArr = ColorRGBA.toColorRGBA(data); + assertEquals(3,colorArr.length); + + ColorRGBA expected1 = new ColorRGBA(0.1f, 0.2f, 0.3f, 0.4f); + ColorRGBA expected2 = new ColorRGBA(0.5f, 0.6f, 0.7f, 0.8f); + ColorRGBA expected3 = new ColorRGBA(0.9f, 1.0f, 1.1f, 1.2f); + + assertTrue(expected1.equals(colorArr[0])); + assertTrue(expected2.equals(colorArr[1])); + assertTrue(expected3.equals(colorArr[2])); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java b/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java new file mode 100644 index 0000000000..0a582ba90e --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2016 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.math; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class Vector2fTest { + @Test + public void testEmptyArray() { + double[] data = new double[0]; + Vector2f[] vectorArr = Vector2f.toVector2(data); + assertEquals(0, vectorArr.length); + } + + @Test(expected=AssertionError.class) + public void testArrayWithWrongLength() { + double[] data = {1.0, 2.1, 3.4}; + Vector2f.toVector2(data); + } + + @Test + public void testArrayWithOneVector() { + double[] data = {1.0, 2.0,}; + Vector2f[] vectorArr = Vector2f.toVector2(data); + assertEquals(1, vectorArr.length); + Vector2f expected = new Vector2f(1.0f, 2.0f); + assertTrue(expected.equals(vectorArr[0])); + } + + @Test + public void testArrayWithMultipleVectors() { + double[] data = { + 1.0, 1.1, + 2.0, 2.1, + 3.0, 3.1, + }; + Vector2f[] vectorArr = Vector2f.toVector2(data); + assertEquals(3, vectorArr.length); + Vector2f expected1 = new Vector2f(1.0f, 1.1f); + Vector2f expected2 = new Vector2f(2.0f, 2.1f); + Vector2f expected3 = new Vector2f(3.0f, 3.1f); + + assertTrue(expected1.equals(vectorArr[0])); + assertTrue(expected2.equals(vectorArr[1])); + assertTrue(expected3.equals(vectorArr[2])); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java b/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java new file mode 100644 index 0000000000..85d42f9086 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2016 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.math; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class Vector3fTest { + + @Test + public void testEmptyArray() { + double[] data = new double[0]; + Vector3f[] vectorArr = Vector3f.toVector3(data); + assertEquals(0, vectorArr.length); + } + + @Test(expected=AssertionError.class) + public void testArrayWithWrongLength() { + double[] data = {1.0, 5.3}; + Vector3f.toVector3(data); + } + + @Test + public void testArrayWithOneVector() { + double[] data = {1.0, 5.3, 4.2}; + Vector3f[] vectorArr = Vector3f.toVector3(data); + assertEquals(1, vectorArr.length); + Vector3f expected = new Vector3f(1.0f, 5.3f, 4.2f); + assertTrue(expected.equals(vectorArr[0])); + } + + @Test + public void testArrayWithMultipleVectors() { + double[] data = { + 1.0, 1.1, 1.2, + 2.0, 2.1, 2.2, + 3.0, 3.1, 3.2, + }; + Vector3f[] vectorArr = Vector3f.toVector3(data); + assertEquals(3, vectorArr.length); + Vector3f expected1 = new Vector3f(1.0f, 1.1f, 1.2f); + Vector3f expected2 = new Vector3f(2.0f, 2.1f, 2.2f); + Vector3f expected3 = new Vector3f(3.0f, 3.1f, 3.2f); + + assertTrue(expected1.equals(vectorArr[0])); + assertTrue(expected2.equals(vectorArr[1])); + assertTrue(expected3.equals(vectorArr[2])); + } +} diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index dabbe62a05..0ae6aebfbf 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -10,6 +10,11 @@ sourceSets { srcDir 'src/xml/java' } } + test { + java { + srcDir 'src/test/java' + } + } } dependencies { 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 491301c226..0ba52426e7 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 @@ -57,8 +57,6 @@ 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; @@ -76,9 +74,11 @@ 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.scene.plugins.fbx.loaders.FbxMaterialLoader; +import com.jme3.scene.plugins.fbx.loaders.FbxTextureLoader; +import com.jme3.scene.plugins.fbx.loaders.PropertyLink; import com.jme3.texture.Image; import com.jme3.texture.Texture; -import com.jme3.texture.Texture2D; import com.jme3.util.BufferUtils; /** @@ -89,1496 +89,1345 @@ * @author Aleksandra Menshchikova */ public class SceneLoader implements AssetLoader { - - private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); - - private AssetManager assetManager; - 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 - - // Loaded objects data - private Map meshDataMap = new HashMap(); - private Map matDataMap = new HashMap(); - private Map texDataMap = new HashMap(); - private Map imgDataMap = new HashMap(); // Video clips - private Map modelDataMap = new HashMap(); // Mesh nodes and limb nodes - private Map poseDataMap = new HashMap(); // Node bind poses - private Map skinMap = new HashMap(); // Skin for bone clusters - private Map clusterMap = new HashMap(); // Bone skinning cluster - private Map acurveMap = new HashMap(); // Animation curves - private Map anodeMap = new HashMap(); // Animation nodes - private Map alayerMap = new HashMap(); // Amination layers - private Map> refMap = new HashMap>(); // Object links - private Map> propMap = new HashMap>(); // Property links - - // Scene objects - private Map modelMap = new HashMap(); // Mesh nodes - private Map limbMap = new HashMap(); // Bones - private Map bindMap = new HashMap(); // Node bind poses - private Map geomMap = new HashMap(); // Mesh geometries - private Map matMap = new HashMap(); - private Map texMap = new HashMap(); - private Map imgMap = new HashMap(); - private Skeleton skeleton; - private AnimControl animControl; - - @Override - public Object load(AssetInfo assetInfo) throws IOException { - this.assetManager = assetInfo.getManager(); - AssetKey assetKey = assetInfo.getKey(); - if(assetKey instanceof SceneKey) - animList = ((SceneKey) assetKey).getAnimations(); - else if(!(assetKey instanceof ModelKey)) - throw new AssetLoadException("Invalid asset key"); - InputStream stream = assetInfo.openStream(); - Node sceneNode = null; - try { - sceneFilename = assetKey.getName(); - sceneFolderName = assetKey.getFolder(); - String ext = assetKey.getExtension(); - sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1); - if(sceneFolderName != null && sceneFolderName.length() > 0) - sceneName = sceneName.substring(sceneFolderName.length()); - reset(); - loadScene(stream); - sceneNode = linkScene(); - } finally { - releaseObjects(); - if(stream != null) { - stream.close(); - } - } - return sceneNode; - } - - private void reset() { - unitSize = 1; - animFrameRate = 30; - } - - private void loadScene(InputStream stream) throws IOException { - logger.log(Level.FINE, "Loading scene {0}", sceneFilename); - long startTime = System.currentTimeMillis(); - FbxFile scene = FbxReader.readFBX(stream); - for(FbxElement e : scene.rootElements) { - if(e.id.equals("GlobalSettings")) - loadGlobalSettings(e); - else if(e.id.equals("Objects")) - loadObjects(e); - else if(e.id.equals("Connections")) - loadConnections(e); - } - long estimatedTime = System.currentTimeMillis() - startTime; - logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime); - } - - private void loadGlobalSettings(FbxElement element) { - for(FbxElement e : element.children) { - if(e.id.equals("Properties70")) { - for(FbxElement e2 : e.children) { - if(e2.id.equals("P")) { - String propName = (String) e2.properties.get(0); - if(propName.equals("UnitScaleFactor")) - this.unitSize = ((Double) e2.properties.get(4)).floatValue(); - else if(propName.equals("CustomFrameRate")) { - float framerate = ((Double) e2.properties.get(4)).floatValue(); - if(framerate != -1) - this.animFrameRate = framerate; - } - } - } - } - } - } - - private void loadObjects(FbxElement element) { - for(FbxElement e : element.children) { - if(e.id.equals("Geometry")) - loadGeometry(e); - else if(e.id.equals("Material")) - loadMaterial(e); - else if(e.id.equals("Model")) - loadModel(e); - else if(e.id.equals("Pose")) - loadPose(e); - else if(e.id.equals("Texture")) - loadTexture(e); - else if(e.id.equals("Video")) - loadImage(e); - else if(e.id.equals("Deformer")) - loadDeformer(e); - else if(e.id.equals("AnimationLayer")) - loadAnimLayer(e); - else if(e.id.equals("AnimationCurve")) - loadAnimCurve(e); - else if(e.id.equals("AnimationCurveNode")) - loadAnimNode(e); - } - } - - private void loadGeometry(FbxElement element) { - long id = (Long) element.properties.get(0); - String type = (String) element.properties.get(2); - if(type.equals("Mesh")) { - MeshData data = new MeshData(); - for(FbxElement e : element.children) { - if(e.id.equals("Vertices")) - data.vertices = (double[]) e.properties.get(0); - else if(e.id.equals("PolygonVertexIndex")) - data.indices = (int[]) e.properties.get(0); - // TODO edges are not used now - //else if(e.id.equals("Edges")) - // data.edges = (int[]) e.properties.get(0); - else if(e.id.equals("LayerElementNormal")) - for(FbxElement e2 : e.children) { - if(e2.id.equals("MappingInformationType")) { - data.normalsMapping = (String) e2.properties.get(0); - if(!data.normalsMapping.equals("ByVertice") && !data.normalsMapping.equals("ByPolygonVertex")) - throw new AssetLoadException("Not supported LayerElementNormal.MappingInformationType = " + data.normalsMapping); - } else if(e2.id.equals("ReferenceInformationType")) { - data.normalsReference = (String) e2.properties.get(0); - if(!data.normalsReference.equals("Direct")) - throw new AssetLoadException("Not supported LayerElementNormal.ReferenceInformationType = " + data.normalsReference); - } else if(e2.id.equals("Normals")) - data.normals = (double[]) e2.properties.get(0); - } - else if(e.id.equals("LayerElementTangent")) - for(FbxElement e2 : e.children) { - if(e2.id.equals("MappingInformationType")) { - data.tangentsMapping = (String) e2.properties.get(0); - if(!data.tangentsMapping.equals("ByVertice") && !data.tangentsMapping.equals("ByPolygonVertex")) - throw new AssetLoadException("Not supported LayerElementTangent.MappingInformationType = " + data.tangentsMapping); - } else if(e2.id.equals("ReferenceInformationType")) { - data.tangentsReference = (String) e2.properties.get(0); - if(!data.tangentsReference.equals("Direct")) - throw new AssetLoadException("Not supported LayerElementTangent.ReferenceInformationType = " + data.tangentsReference); - } else if(e2.id.equals("Tangents")) - data.tangents = (double[]) e2.properties.get(0); - } - else if(e.id.equals("LayerElementBinormal")) - for(FbxElement e2 : e.children) { - if(e2.id.equals("MappingInformationType")) { - data.binormalsMapping = (String) e2.properties.get(0); - if(!data.binormalsMapping.equals("ByVertice") && !data.binormalsMapping.equals("ByPolygonVertex")) - throw new AssetLoadException("Not supported LayerElementBinormal.MappingInformationType = " + data.binormalsMapping); - } else if(e2.id.equals("ReferenceInformationType")) { - data.binormalsReference = (String) e2.properties.get(0); - if(!data.binormalsReference.equals("Direct")) - throw new AssetLoadException("Not supported LayerElementBinormal.ReferenceInformationType = " + data.binormalsReference); - } else if(e2.id.equals("Tangents")) - data.binormals = (double[]) e2.properties.get(0); - } - else if(e.id.equals("LayerElementUV")) - for(FbxElement e2 : e.children) { - if(e2.id.equals("MappingInformationType")) { - data.uvMapping = (String) e2.properties.get(0); - if(!data.uvMapping.equals("ByPolygonVertex")) - throw new AssetLoadException("Not supported LayerElementUV.MappingInformationType = " + data.uvMapping); - } else if(e2.id.equals("ReferenceInformationType")) { - data.uvReference = (String) e2.properties.get(0); - if(!data.uvReference.equals("IndexToDirect")) - throw new AssetLoadException("Not supported LayerElementUV.ReferenceInformationType = " + data.uvReference); - } else if(e2.id.equals("UV")) - data.uv = (double[]) e2.properties.get(0); - else if(e2.id.equals("UVIndex")) - data.uvIndex = (int[]) e2.properties.get(0); - } - // TODO smoothing is not used now - //else if(e.id.equals("LayerElementSmoothing")) - // for(FbxElement e2 : e.children) { - // if(e2.id.equals("MappingInformationType")) { - // data.smoothingMapping = (String) e2.properties.get(0); - // if(!data.smoothingMapping.equals("ByEdge")) - // throw new AssetLoadException("Not supported LayerElementSmoothing.MappingInformationType = " + data.smoothingMapping); - // } else if(e2.id.equals("ReferenceInformationType")) { - // data.smoothingReference = (String) e2.properties.get(0); - // if(!data.smoothingReference.equals("Direct")) - // throw new AssetLoadException("Not supported LayerElementSmoothing.ReferenceInformationType = " + data.smoothingReference); - // } else if(e2.id.equals("Smoothing")) - // data.smoothing = (int[]) e2.properties.get(0); - // } - else if(e.id.equals("LayerElementMaterial")) - for(FbxElement e2 : e.children) { - if(e2.id.equals("MappingInformationType")) { - data.materialsMapping = (String) e2.properties.get(0); - if(!data.materialsMapping.equals("AllSame")) - throw new AssetLoadException("Not supported LayerElementMaterial.MappingInformationType = " + data.materialsMapping); - } else if(e2.id.equals("ReferenceInformationType")) { - data.materialsReference = (String) e2.properties.get(0); - if(!data.materialsReference.equals("IndexToDirect")) - throw new AssetLoadException("Not supported LayerElementMaterial.ReferenceInformationType = " + data.materialsReference); - } else if(e2.id.equals("Materials")) - data.materials = (int[]) e2.properties.get(0); - } - } - meshDataMap.put(id, data); - } - } - - private void loadMaterial(FbxElement element) { - long id = (Long) element.properties.get(0); - String path = (String) element.properties.get(1); - String type = (String) element.properties.get(2); - if(type.equals("")) { - MaterialData data = new MaterialData(); - data.name = path.substring(0, path.indexOf(0)); - for(FbxElement e : element.children) { - if(e.id.equals("ShadingModel")) { - data.shadingModel = (String) e.properties.get(0); - } else if(e.id.equals("Properties70")) { - for(FbxElement e2 : e.children) { - if(e2.id.equals("P")) { - String propName = (String) e2.properties.get(0); - if(propName.equals("AmbientColor")) { - double x = (Double) e2.properties.get(4); - double y = (Double) e2.properties.get(5); - double z = (Double) e2.properties.get(6); - data.ambientColor.set((float) x, (float) y, (float) z); - } else if(propName.equals("AmbientFactor")) { - double x = (Double) e2.properties.get(4); - data.ambientFactor = (float) x; - } else if(propName.equals("DiffuseColor")) { - double x = (Double) e2.properties.get(4); - double y = (Double) e2.properties.get(5); - double z = (Double) e2.properties.get(6); - data.diffuseColor.set((float) x, (float) y, (float) z); - } else if(propName.equals("DiffuseFactor")) { - double x = (Double) e2.properties.get(4); - data.diffuseFactor = (float) x; - } else if(propName.equals("SpecularColor")) { - double x = (Double) e2.properties.get(4); - double y = (Double) e2.properties.get(5); - double z = (Double) e2.properties.get(6); - data.specularColor.set((float) x, (float) y, (float) z); - } else if(propName.equals("Shininess") || propName.equals("ShininessExponent")) { - double x = (Double) e2.properties.get(4); - data.shininessExponent = (float) x; - } - } - } - } - } - matDataMap.put(id, data); - } - } - - private void loadModel(FbxElement element) { - long id = (Long) element.properties.get(0); - String path = (String) element.properties.get(1); - String type = (String) element.properties.get(2); - ModelData data = new ModelData(); - data.name = path.substring(0, path.indexOf(0)); - data.type = type; - for(FbxElement e : element.children) { - if(e.id.equals("Properties70")) { - for(FbxElement e2 : e.children) { - if(e2.id.equals("P")) { - String propName = (String) e2.properties.get(0); - if(propName.equals("Lcl Translation")) { - double x = (Double) e2.properties.get(4); - double y = (Double) e2.properties.get(5); - double z = (Double) e2.properties.get(6); - data.localTranslation.set((float) x, (float) y, (float) z).divideLocal(unitSize); - } else if(propName.equals("Lcl Rotation")) { - double x = (Double) e2.properties.get(4); - double y = (Double) e2.properties.get(5); - double z = (Double) e2.properties.get(6); - data.localRotation.fromAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD); - } else if(propName.equals("Lcl Scaling")) { - double x = (Double) e2.properties.get(4); - double y = (Double) e2.properties.get(5); - double z = (Double) e2.properties.get(6); - data.localScale.set((float) x, (float) y, (float) z).multLocal(unitSize); - } else if(propName.equals("PreRotation")) { - double x = (Double) e2.properties.get(4); - double y = (Double) e2.properties.get(5); - double z = (Double) e2.properties.get(6); - data.preRotation = quatFromBoneAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD); - } - } - } - } - } - modelDataMap.put(id, data); - } - - private void loadPose(FbxElement element) { - long id = (Long) element.properties.get(0); - String path = (String) element.properties.get(1); - String type = (String) element.properties.get(2); - if(type.equals("BindPose")) { - BindPoseData data = new BindPoseData(); - data.name = path.substring(0, path.indexOf(0)); - for(FbxElement e : element.children) { - if(e.id.equals("PoseNode")) { - NodeTransformData item = new NodeTransformData(); - for(FbxElement e2 : e.children) { - if(e2.id.equals("Node")) - item.nodeId = (Long) e2.properties.get(0); - else if(e2.id.equals("Matrix")) - item.transform = (double[]) e2.properties.get(0); - } - data.list.add(item); - } - } - poseDataMap.put(id, data); - } - } - - private void loadTexture(FbxElement element) { - long id = (Long) element.properties.get(0); - String path = (String) element.properties.get(1); - String type = (String) element.properties.get(2); - if(type.equals("")) { - TextureData data = new TextureData(); - data.name = path.substring(0, path.indexOf(0)); - for(FbxElement e : element.children) { - if(e.id.equals("Type")) - data.bindType = (String) e.properties.get(0); - else if(e.id.equals("FileName")) - data.filename = (String) e.properties.get(0); - } - texDataMap.put(id, data); - } - } - - private void loadImage(FbxElement element) { - long id = (Long) element.properties.get(0); - String path = (String) element.properties.get(1); - String type = (String) element.properties.get(2); - if(type.equals("Clip")) { - ImageData data = new ImageData(); - data.name = path.substring(0, path.indexOf(0)); - for(FbxElement e : element.children) { - if(e.id.equals("Type")) - data.type = (String) e.properties.get(0); - else if(e.id.equals("FileName")) - data.filename = (String) e.properties.get(0); - else if(e.id.equals("RelativeFilename")) - data.relativeFilename = (String) e.properties.get(0); - else if(e.id.equals("Content")) { - if(e.properties.size() > 0) - data.content = (byte[]) e.properties.get(0); - } - } - imgDataMap.put(id, data); - } - } - - private void loadDeformer(FbxElement element) { - long id = (Long) element.properties.get(0); - String type = (String) element.properties.get(2); - if(type.equals("Skin")) { - SkinData skinData = new SkinData(); - for(FbxElement e : element.children) { - if(e.id.equals("SkinningType")) - skinData.type = (String) e.properties.get(0); - } - skinMap.put(id, skinData); - } else if(type.equals("Cluster")) { - ClusterData clusterData = new ClusterData(); - for(FbxElement e : element.children) { - if(e.id.equals("Indexes")) - clusterData.indexes = (int[]) e.properties.get(0); - else if(e.id.equals("Weights")) - clusterData.weights = (double[]) e.properties.get(0); - else if(e.id.equals("Transform")) - clusterData.transform = (double[]) e.properties.get(0); - else if(e.id.equals("TransformLink")) - clusterData.transformLink = (double[]) e.properties.get(0); - } - clusterMap.put(id, clusterData); - } - } - - private void loadAnimLayer(FbxElement element) { - long id = (Long) element.properties.get(0); - String path = (String) element.properties.get(1); - String type = (String) element.properties.get(2); - if(type.equals("")) { - AnimLayer layer = new AnimLayer(); - layer.name = path.substring(0, path.indexOf(0)); - alayerMap.put(id, layer); - } - } - - private void loadAnimCurve(FbxElement element) { - long id = (Long) element.properties.get(0); - String type = (String) element.properties.get(2); - if(type.equals("")) { - AnimCurveData data = new AnimCurveData(); - for(FbxElement e : element.children) { - if(e.id.equals("KeyTime")) - data.keyTimes = (long[]) e.properties.get(0); - else if(e.id.equals("KeyValueFloat")) - data.keyValues = (float[]) e.properties.get(0); - } - acurveMap.put(id, data); - } - } - - private void loadAnimNode(FbxElement element) { - long id = (Long) element.properties.get(0); - String path = (String) element.properties.get(1); - String type = (String) element.properties.get(2); - if(type.equals("")) { - Double x = null, y = null, z = null; - for(FbxElement e : element.children) { - if(e.id.equals("Properties70")) { - for(FbxElement e2 : e.children) { - if(e2.id.equals("P")) { - String propName = (String) e2.properties.get(0); - if(propName.equals("d|X")) - x = (Double) e2.properties.get(4); - else if(propName.equals("d|Y")) - y = (Double) e2.properties.get(4); - else if(propName.equals("d|Z")) - z = (Double) e2.properties.get(4); - } - } - } - } - // Load only T R S curve nodes - if(x != null && y != null && z != null) { - AnimNode node = new AnimNode(); - node.value = new Vector3f(x.floatValue(), y.floatValue(), z.floatValue()); - node.name = path.substring(0, path.indexOf(0)); - anodeMap.put(id, node); - } - } - } - - private void loadConnections(FbxElement element) { - for(FbxElement e : element.children) { - if(e.id.equals("C")) { - String type = (String) e.properties.get(0); - long objId, refId; - if(type.equals("OO")) { - objId = (Long) e.properties.get(1); - refId = (Long) e.properties.get(2); - List links = refMap.get(objId); - if(links == null) { - links = new ArrayList(); - refMap.put(objId, links); - } - links.add(refId); - } else if(type.equals("OP")) { - objId = (Long) e.properties.get(1); - refId = (Long) e.properties.get(2); - String propName = (String) e.properties.get(3); - List props = propMap.get(objId); - if(props == null) { - props = new ArrayList(); - propMap.put(objId, props); - } - props.add(new PropertyLink(refId, propName)); - } - } - } - } - - private Geometry createGeomerty(MeshData data) { - Mesh mesh = new Mesh(); - mesh.setMode(Mode.Triangles); - // Since each vertex should contain unique texcoord and normal we should unroll vertex indexing - // So we don't use VertexBuffer.Type.Index for elements drawing - // Moreover quads should be triangulated (this increases number of vertices) - boolean isQuads = false; - if(data.indices != null) { - data.iCount = data.indices.length; - data.srcVertexCount = data.vertices.length / 3; - // Indices contains negative numbers to define polygon last index - // Check indices strides to be sure we have triangles or quads - boolean allTriangles = true; - boolean allQads = true; - for(int i = 0; i < data.indices.length; ++i) { - if(i % 3 == 2) { // Triangle stride - if(data.indices[i] >= 0) - allTriangles = false; - } else { - if(data.indices[i] < 0) - allTriangles = false; - } - if(i % 4 == 3) { // Quad stride - if(data.indices[i] >= 0) - allQads = false; - } else { - if(data.indices[i] < 0) - allQads = false; - } - } - if(allTriangles) { - isQuads = false; - data.vCount = data.iCount; - } else if(allQads) { - isQuads = true; - data.vCount = 6 * (data.iCount / 4); // Each quad will be splited into two triangles - } else - throw new AssetLoadException("Unsupported PolygonVertexIndex stride"); - data.vertexMap = new int[data.vCount]; - data.indexMap = new int[data.vCount]; - // Unroll index array into vertex mapping - int n = 0; - for(int i = 0; i < data.iCount; ++i) { - int index = data.indices[i]; - if(index < 0) { - int lastIndex = -(index + 1); - if(isQuads) { - data.vertexMap[n + 0] = data.indices[i - 3]; - data.vertexMap[n + 1] = data.indices[i - 2]; - data.vertexMap[n + 2] = data.indices[i - 1]; - data.vertexMap[n + 3] = data.indices[i - 3]; - data.vertexMap[n + 4] = data.indices[i - 1]; - data.vertexMap[n + 5] = lastIndex; - data.indexMap[n + 0] = (i - 3); - data.indexMap[n + 1] = (i - 2); - data.indexMap[n + 2] = (i - 1); - data.indexMap[n + 3] = (i - 3); - data.indexMap[n + 4] = (i - 1); - data.indexMap[n + 5] = (i - 0); - n += 6; - } else { - data.vertexMap[n + 0] = data.indices[i - 2]; - data.vertexMap[n + 1] = data.indices[i - 1]; - data.vertexMap[n + 2] = lastIndex; - data.indexMap[n + 0] = (i - 2); - data.indexMap[n + 1] = (i - 1); - data.indexMap[n + 2] = (i - 0); - n += 3; - } - } - } - // Build reverse vertex mapping - data.reverseVertexMap = new ArrayList>(data.srcVertexCount); - for(int i = 0; i < data.srcVertexCount; ++i) - data.reverseVertexMap.add(new ArrayList()); - for(int i = 0; i < data.vCount; ++i) { - int index = data.vertexMap[i]; - data.reverseVertexMap.get(index).add(i); - } - } else { - // Stub for no vertex indexing (direct mapping) - data.iCount = data.vCount = data.srcVertexCount; - data.vertexMap = new int[data.vCount]; - data.indexMap = new int[data.vCount]; - data.reverseVertexMap = new ArrayList>(data.vCount); - for(int i = 0; i < data.vCount; ++i) { - data.vertexMap[i] = i; - data.indexMap[i] = i; - List reverseIndices = new ArrayList(1); - reverseIndices.add(i); - data.reverseVertexMap.add(reverseIndices); - } - } - if(data.vertices != null) { - // Unroll vertices data array - FloatBuffer posBuf = BufferUtils.createFloatBuffer(data.vCount * 3); - mesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf); - int srcCount = data.vertices.length / 3; - for(int i = 0; i < data.vCount; ++i) { - int index = data.vertexMap[i]; - if(index > srcCount) - throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount); - float x = (float) data.vertices[3 * index + 0] / unitSize; - float y = (float) data.vertices[3 * index + 1] / unitSize; - float z = (float) data.vertices[3 * index + 2] / unitSize; - posBuf.put(x).put(y).put(z); - } - } - if(data.normals != null) { - // Unroll normals data array - FloatBuffer normBuf = BufferUtils.createFloatBuffer(data.vCount * 3); - mesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf); - int[] mapping = null; - if(data.normalsMapping.equals("ByVertice")) - mapping = data.vertexMap; - else if(data.normalsMapping.equals("ByPolygonVertex")) - mapping = data.indexMap; - int srcCount = data.normals.length / 3; - for(int i = 0; i < data.vCount; ++i) { - int index = mapping[i]; - if(index > srcCount) - throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount); - float x = (float) data.normals[3 * index + 0]; - float y = (float) data.normals[3 * index + 1]; - float z = (float) data.normals[3 * index + 2]; - normBuf.put(x).put(y).put(z); - } - } - if(data.tangents != null) { - // Unroll normals data array - FloatBuffer tanBuf = BufferUtils.createFloatBuffer(data.vCount * 4); - mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tanBuf); - int[] mapping = null; - if(data.tangentsMapping.equals("ByVertice")) - mapping = data.vertexMap; - else if(data.tangentsMapping.equals("ByPolygonVertex")) - mapping = data.indexMap; - int srcCount = data.tangents.length / 3; - for(int i = 0; i < data.vCount; ++i) { - int index = mapping[i]; - if(index > srcCount) - throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount); - float x = (float) data.tangents[3 * index + 0]; - float y = (float) data.tangents[3 * index + 1]; - float z = (float) data.tangents[3 * index + 2]; - tanBuf.put(x).put(y).put(z).put(-1.0f); - } - } - if(data.binormals != null) { - // Unroll normals data array - FloatBuffer binormBuf = BufferUtils.createFloatBuffer(data.vCount * 3); - mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormBuf); - int[] mapping = null; - if(data.binormalsMapping.equals("ByVertice")) - mapping = data.vertexMap; - else if(data.binormalsMapping.equals("ByPolygonVertex")) - mapping = data.indexMap; - int srcCount = data.binormals.length / 3; - for(int i = 0; i < data.vCount; ++i) { - int index = mapping[i]; - if(index > srcCount) - throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount); - float x = (float) data.binormals[3 * index + 0]; - float y = (float) data.binormals[3 * index + 1]; - float z = (float) data.binormals[3 * index + 2]; - binormBuf.put(x).put(y).put(z); - } - } - if(data.uv != null) { - int[] unIndexMap = data.vertexMap; - if(data.uvIndex != null) { - int uvIndexSrcCount = data.uvIndex.length; - if(uvIndexSrcCount != data.iCount) - throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + data.iCount); - // Unroll UV index array - unIndexMap = new int[data.vCount]; - int n = 0; - for(int i = 0; i < data.iCount; ++i) { - int index = data.uvIndex[i]; - if(isQuads && (i % 4) == 3) { - unIndexMap[n + 0] = data.uvIndex[i - 3]; - unIndexMap[n + 1] = data.uvIndex[i - 1]; - unIndexMap[n + 2] = index; - n += 3; - } else { - unIndexMap[i] = index; - } - } - } - // Unroll UV data array - FloatBuffer tcBuf = BufferUtils.createFloatBuffer(data.vCount * 2); - mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, tcBuf); - int srcCount = data.uv.length / 2; - for(int i = 0; i < data.vCount; ++i) { - int index = unIndexMap[i]; - if(index > srcCount) - throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount); - float u = 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); - m.getAdditionalRenderState().setAlphaTest(true); - m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); - return m; - } - - private Node createNode(ModelData data) { - Node model = new Node(data.name); - model.setLocalTranslation(data.localTranslation); - model.setLocalRotation(data.localRotation); - model.setLocalScale(data.localScale); - return model; - } - - private Limb createLimb(ModelData data) { - Limb limb = new Limb(); - limb.name = data.name; - Quaternion rotation = data.preRotation.mult(data.localRotation); - limb.bindTransform = new Transform(data.localTranslation, rotation, data.localScale); - return limb; - } - - private BindPose createPose(BindPoseData data) { - BindPose pose = new BindPose(); - pose.name = data.name; - for(NodeTransformData item : data.list) { - Transform t = buildTransform(item.transform); - //t.getTranslation().divideLocal(unitSize); - t.getScale().multLocal(unitSize); - pose.nodeTransforms.put(item.nodeId, t); - } - return pose; - } - - private Texture createTexture(TextureData data) { - Texture tex = new Texture2D(); - tex.setName(data.name); - return tex; - } - - private Image createImage(ImageData data) { - Image image = null; - if(data.filename != null) { - // Try load by absolute path - File file = new File(data.filename); - if(file.exists() && file.isFile()) { - File dir = new File(file.getParent()); - String locatorPath = dir.getAbsolutePath(); - Texture tex = null; - try { - assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); - tex = assetManager.loadTexture(file.getName()); - } catch(Exception e) { - } finally { - assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); - } - if(tex != null) - image = tex.getImage(); - } - } - if(image == null && data.relativeFilename != null) { - // Try load by relative path - File dir = new File(sceneFolderName); - String locatorPath = dir.getAbsolutePath(); - Texture tex = null; - try { - assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); - tex = assetManager.loadTexture(data.relativeFilename); - } catch(Exception e) { - } finally { - assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); - } - if(tex != null) - image = tex.getImage(); - } - if(image == null && data.content != null) { - // Try load from content - String filename = null; - if(data.filename != null) - filename = new File(data.filename).getName(); - if(filename != null && data.relativeFilename != null) - filename = data.relativeFilename; - // Filename is required to aquire asset loader by extension - if(filename != null) { - String locatorPath = sceneFilename; - filename = sceneFilename + File.separatorChar + filename; // Unique path - Texture tex = null; - try { - assetManager.registerLocator(locatorPath, com.jme3.scene.plugins.fbx.ContentTextureLocator.class); - tex = assetManager.loadTexture(new ContentTextureKey(filename, data.content)); - } catch(Exception e) { - } finally { - assetManager.unregisterLocator(locatorPath, com.jme3.scene.plugins.fbx.ContentTextureLocator.class); - } - if(tex != null) - image = tex.getImage(); - } - } - if(image == null) - throw new AssetLoadException("Content not loaded for image " + data.name); - return image; - } - - private Transform buildTransform(double[] transform) { - float[] m = new float[transform.length]; - for(int i = 0; i < transform.length; ++i) - m[i] = (float) transform[i]; - Matrix4f matrix = new Matrix4f(m); - Vector3f pos = matrix.toTranslationVector(); - Quaternion rot = matrix.toRotationQuat(); - Vector3f scale = matrix.toScaleVector(); - return new Transform(pos, rot, scale); - } - - private Quaternion quatFromBoneAngles(float xAngle, float yAngle, float zAngle) { - float angle; - float sinY, sinZ, sinX, cosY, cosZ, cosX; - angle = zAngle * 0.5f; - sinZ = FastMath.sin(angle); - cosZ = FastMath.cos(angle); - angle = yAngle * 0.5f; - sinY = FastMath.sin(angle); - cosY = FastMath.cos(angle); - angle = xAngle * 0.5f; - sinX = FastMath.sin(angle); - cosX = FastMath.cos(angle); - float cosYXcosZ = cosY * cosZ; - float sinYXsinZ = sinY * sinZ; - float cosYXsinZ = cosY * sinZ; - float sinYXcosZ = sinY * cosZ; - // For some reason bone space is differ, this is modified formulas - float w = (cosYXcosZ * cosX + sinYXsinZ * sinX); - float x = (cosYXcosZ * sinX - sinYXsinZ * cosX); - float y = (sinYXcosZ * cosX + cosYXsinZ * sinX); - float z = (cosYXsinZ * cosX - sinYXcosZ * sinX); - return new Quaternion(x, y, z, w).normalizeLocal(); - } - - private Node linkScene() { - logger.log(Level.FINE, "Linking scene objects"); - long startTime = System.currentTimeMillis(); - Node sceneNode = linkSceneNodes(); - linkMaterials(); - linkMeshes(sceneNode); - linkSkins(sceneNode); - linkAnimations(); - long estimatedTime = System.currentTimeMillis() - startTime; - logger.log(Level.FINE, "Linking done in {0} ms", estimatedTime); - return sceneNode; - } - - private Node linkSceneNodes() { - Node sceneNode = new Node(sceneName + "-scene"); - // Build mesh nodes - for(long nodeId : modelDataMap.keySet()) { - ModelData data = modelDataMap.get(nodeId); - Node node = createNode(data); - modelMap.put(nodeId, node); - } - // Link model nodes into scene - for(long modelId : modelMap.keySet()) { - List refs = refMap.get(modelId); - if(refs == null) - continue; - Node model = modelMap.get(modelId); - for(long refId : refs) { - Node rootNode = (refId != 0) ? modelMap.get(refId) : sceneNode; - if(rootNode != null) - rootNode.attachChild(model); - } - } - // Build bind poses - for(long poseId : poseDataMap.keySet()) { - BindPoseData data = poseDataMap.get(poseId); - BindPose pose = createPose(data); - if(pose != null) - bindMap.put(poseId, pose); - } - // Apply bind poses - for(BindPose pose : bindMap.values()) { - for(long nodeId : pose.nodeTransforms.keySet()) { - Node model = modelMap.get(nodeId); - if(model != null) { - Transform t = pose.nodeTransforms.get(nodeId); - model.setLocalTransform(t); - } - } - } - return sceneNode; - } - - private void linkMaterials() { - // Build materials - for(long matId : matDataMap.keySet()) { - MaterialData data = matDataMap.get(matId); - Material material = createMaterial(data); - if(material != null) - matMap.put(matId, material); - } - // Build textures - for(long texId : texDataMap.keySet()) { - TextureData data = texDataMap.get(texId); - Texture tex = createTexture(data); - if(tex != null) - texMap.put(texId, tex); - } - // Build Images - for(long imgId : imgDataMap.keySet()) { - ImageData data = imgDataMap.get(imgId); - Image img = createImage(data); - if(img != null) - imgMap.put(imgId, img); - } - // Link images to textures - for(long imgId : imgMap.keySet()) { - List refs = refMap.get(imgId); - if(refs == null) - continue; - Image img = imgMap.get(imgId); - for(long refId : refs) { - Texture tex = texMap.get(refId); - if(tex != null) - tex.setImage(img); - } - } - // Link textures to material maps - for(long texId : texMap.keySet()) { - List props = propMap.get(texId); - if(props == null) - continue; - Texture tex = texMap.get(texId); - for(PropertyLink prop : props) { - Material mat = matMap.get(prop.ref); - if(mat != null) { - if(prop.propName.equals("DiffuseColor")) { - mat.setTexture("DiffuseMap", tex); - mat.setColor("Diffuse", ColorRGBA.White); - } else if(prop.propName.equals("SpecularColor")) { - mat.setTexture("SpecularMap", tex); - mat.setColor("Specular", ColorRGBA.White); - } else if(prop.propName.equals("NormalMap")) - mat.setTexture("NormalMap", tex); - } - } - } - } - - private void linkMeshes(Node sceneNode) { - // Build meshes - for(long meshId : meshDataMap.keySet()) { - MeshData data = meshDataMap.get(meshId); - Geometry geom = createGeomerty(data); - if(geom != null) - geomMap.put(meshId, geom); - } - // Link meshes to models - for(long geomId : geomMap.keySet()) { - List refs = refMap.get(geomId); - if(refs == null) - continue; - Geometry geom = geomMap.get(geomId); - for(long refId : refs) { - Node rootNode = (refId != 0) ? modelMap.get(refId) : sceneNode; - if(rootNode != null) { - geom.setName(rootNode.getName() + "-mesh"); - geom.updateModelBound(); - rootNode.attachChild(geom); - break; - } - } - } - // Link materials to meshes - for(long matId : matMap.keySet()) { - List refs = refMap.get(matId); - if(refs == null) - continue; - Material mat = matMap.get(matId); - for(long refId : refs) { - Node rootNode = modelMap.get(refId); - if(rootNode != null) { - for(Spatial child : rootNode.getChildren()) - child.setMaterial(mat); - } - } - } - } - - private void linkSkins(Node sceneNode) { - // Build skeleton limbs - for(long nodeId : modelDataMap.keySet()) { - ModelData data = modelDataMap.get(nodeId); - if(data.type.equals("LimbNode")) { - Limb limb = createLimb(data); - limbMap.put(nodeId, limb); - } - } - if(limbMap.size() == 0) - return; - // Build skeleton bones - Map bones = new HashMap(); - for(long limbId : limbMap.keySet()) { - Limb limb = limbMap.get(limbId); - Bone bone = new Bone(limb.name); - Transform t = limb.bindTransform; - bone.setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale()); - bones.put(limbId, bone); - } - // Attach bones to roots - for(long limbId : limbMap.keySet()) { - List refs = refMap.get(limbId); - if(refs == null) - continue; - // Find root limb - long rootLimbId = 0L; - for(long refId : refs) { - if(limbMap.containsKey(refId)) { - rootLimbId = refId; - break; - } - } - if(rootLimbId != 0L) { - Bone bone = bones.get(limbId); - Bone root = bones.get(rootLimbId); - root.addChild(bone); - } - } - // Link bone clusters to skin - for(long clusterId : clusterMap.keySet()) { - List refs = refMap.get(clusterId); - if(refs == null) - continue; - for(long skinId : refs) { - if(skinMap.containsKey(skinId)) { - ClusterData data = clusterMap.get(clusterId); - data.skinId = skinId; - break; - } - } - } - // Build the skeleton - this.skeleton = new Skeleton(bones.values().toArray(new Bone[0])); - skeleton.setBindingPose(); - for(long limbId : bones.keySet()) { - Bone bone = bones.get(limbId); - Limb limb = limbMap.get(limbId); - limb.boneIndex = skeleton.getBoneIndex(bone); - } - // Assign bones skinning to meshes - for(long skinId : skinMap.keySet()) { - // Find mesh by skin - Mesh mesh = null; - MeshData meshData = null; - for(long meshId : refMap.get(skinId)) { - Geometry g = geomMap.get(meshId); - if(g != null) { - meshData = meshDataMap.get(meshId); - mesh = g.getMesh(); - break; - } - } - // Bind skinning indexes and weight - if(mesh != null && meshData != null) { - // Create bone buffers - FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(meshData.vCount * 4); - ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(meshData.vCount * 4); - mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeightData); - mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndicesData); - mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(Usage.CpuOnly); - mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(Usage.CpuOnly); - VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight); - VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex); - indicesHW.setUsage(Usage.CpuOnly); // Setting usage to CpuOnly so that the buffer is not send empty to the GPU - weightsHW.setUsage(Usage.CpuOnly); - mesh.setBuffer(weightsHW); - mesh.setBuffer(indicesHW); - // Accumulate skin bones influence into mesh buffers - boolean bonesLimitExceeded = false; - for(long limbId : bones.keySet()) { - // Search bone cluster for the given limb and skin - ClusterData cluster = null; - for(long clusterId : refMap.get(limbId)) { - ClusterData data = clusterMap.get(clusterId); - if(data != null && data.skinId == skinId) { - cluster = data; - break; - } - } - if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length) - continue; - Limb limb = limbMap.get(limbId); - if(limb.boneIndex > 255) - throw new AssetLoadException("Bone index can't be packed into byte"); - for(int i = 0; i < cluster.indexes.length; ++i) { - int vertexIndex = cluster.indexes[i]; - if(vertexIndex >= meshData.reverseVertexMap.size()) - throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + meshData.reverseVertexMap.size()); - List dstVertices = meshData.reverseVertexMap.get(vertexIndex); - for(int v : dstVertices) { - // Append bone index and weight to vertex - int offset; - float w = 0; - for(offset = v * 4; offset < v * 4 + 4; ++offset) { - w = boneWeightData.get(offset); - if(w == 0) - break; - } - if(w == 0) { - boneWeightData.put(offset, (float) cluster.weights[i]); - boneIndicesData.put(offset, (byte) limb.boneIndex); - } else { - // TODO It would be nice to discard small weight to accumulate more heavy influence - bonesLimitExceeded = true; - } - } - } - } - if(bonesLimitExceeded) - logger.log(Level.WARNING, "Skinning support max 4 bone per vertex. Exceeding data will be discarded."); - // Postprocess bones weights - int maxWeightsPerVert = 0; - boneWeightData.rewind(); - for(int v = 0; v < meshData.vCount; v++) { - float w0 = boneWeightData.get(); - float w1 = boneWeightData.get(); - float w2 = boneWeightData.get(); - float w3 = boneWeightData.get(); - if(w3 != 0) { - maxWeightsPerVert = Math.max(maxWeightsPerVert, 4); - } else if(w2 != 0) { - maxWeightsPerVert = Math.max(maxWeightsPerVert, 3); - } else if(w1 != 0) { - maxWeightsPerVert = Math.max(maxWeightsPerVert, 2); - } else if(w0 != 0) { - maxWeightsPerVert = Math.max(maxWeightsPerVert, 1); - } - float sum = w0 + w1 + w2 + w3; - if(sum != 1f) { - // normalize weights - float mult = (sum != 0) ? (1f / sum) : 0; - boneWeightData.position(v * 4); - boneWeightData.put(w0 * mult); - boneWeightData.put(w1 * mult); - boneWeightData.put(w2 * mult); - boneWeightData.put(w3 * mult); - } - } - mesh.setMaxNumWeights(maxWeightsPerVert); - mesh.generateBindPose(true); - } - } - // Attach controls - animControl = new AnimControl(skeleton); - sceneNode.addControl(animControl); - SkeletonControl control = new SkeletonControl(skeleton); - sceneNode.addControl(control); - } - - private void linkAnimations() { - if(skeleton == null) - return; - if(animList == null || animList.list.size() == 0) - return; - // Link curves to nodes - for(long curveId : acurveMap.keySet()) { - List props = propMap.get(curveId); - if(props == null) - continue; - AnimCurveData acurve = acurveMap.get(curveId); - for(PropertyLink prop : props) { - AnimNode anode = anodeMap.get(prop.ref); - if(anode != null) { - if(prop.propName.equals("d|X")) - anode.xCurve = acurve; - else if(prop.propName.equals("d|Y")) - anode.yCurve = acurve; - else if(prop.propName.equals("d|Z")) - anode.zCurve = acurve; - } - } - } - // Link nodes to layers - for(long nodeId : anodeMap.keySet()) { - List refs = refMap.get(nodeId); - if(refs == null) - continue; - for(long layerId : refs) { - if(alayerMap.containsKey(layerId)) { - AnimNode anode = anodeMap.get(nodeId); - anode.layerId = layerId; - break; - } - } - } - // Extract aminations - HashMap anims = new HashMap(); - for(AnimInverval animInfo : animList.list) { - float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate; - float animStart = animInfo.firstFrame / this.animFrameRate; - float animStop = animInfo.lastFrame / this.animFrameRate; - Animation anim = new Animation(animInfo.name, length); - // Search source layer for animation nodes - long sourceLayerId = 0L; - for(long layerId : alayerMap.keySet()) { - AnimLayer layer = alayerMap.get(layerId); - if(layer.name.equals(animInfo.layerName)) { - sourceLayerId = layerId; - break; - } - } - // Assign animation nodes to limbs - for(Limb limb : limbMap.values()) { - limb.animTranslation = null; - limb.animRotation = null; - limb.animScale = null; - } - for(long nodeId : anodeMap.keySet()) { - List props = propMap.get(nodeId); - if(props == null) - continue; - AnimNode anode = anodeMap.get(nodeId); - if(sourceLayerId != 0L && anode.layerId != sourceLayerId) - continue; // filter node - for(PropertyLink prop : props) { - Limb limb = limbMap.get(prop.ref); - if(limb != null) { - if(prop.propName.equals("Lcl Translation")) - limb.animTranslation = anode; - else if(prop.propName.equals("Lcl Rotation")) - limb.animRotation = anode; - else if(prop.propName.equals("Lcl Scaling")) - limb.animScale = anode; - } - } - } - // Build bone tracks - for(Limb limb : limbMap.values()) { - long[] keyTimes = null; - boolean haveTranslation = (limb.animTranslation != null && limb.animTranslation.xCurve != null && limb.animTranslation.yCurve != null && limb.animTranslation.zCurve != null); - boolean haveRotation = (limb.animRotation != null && limb.animRotation.xCurve != null && limb.animRotation.yCurve != null && limb.animRotation.zCurve != null); - boolean haveScale = (limb.animScale != null && limb.animScale.xCurve != null && limb.animScale.yCurve != null && limb.animScale.zCurve != null); - // Search key time array - if(haveTranslation) - keyTimes = limb.animTranslation.xCurve.keyTimes; - else if(haveRotation) - keyTimes = limb.animRotation.xCurve.keyTimes; - else if(haveScale) - keyTimes = limb.animScale.xCurve.keyTimes; - if(keyTimes == null) - continue; - // Calculate keys interval by animation time interval - int firstKey = 0; - int lastKey = keyTimes.length - 1; - for(int i = 0; i < keyTimes.length; ++i) { - float time = (float) (((double) keyTimes[i]) * secondsPerUnit); // Translate into seconds - if(time <= animStart) - firstKey = i; - if(time >= animStop) { - lastKey = i; - break; - } - } - int keysCount = lastKey - firstKey + 1; - if(keysCount <= 0) - continue; - float[] times = new float[keysCount]; - Vector3f[] translations = new Vector3f[keysCount]; - Quaternion[] rotations = new Quaternion[keysCount]; - Vector3f[] scales = null; - // Calculate keyframes times - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKey + i; - float time = (float) (((double) keyTimes[keyIndex]) * secondsPerUnit); // Translate into seconds - times[i] = time - animStart; - } - // Load keyframes from animation curves - if(haveTranslation) { - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKey + i; - float x = limb.animTranslation.xCurve.keyValues[keyIndex] - limb.animTranslation.value.x; - float y = limb.animTranslation.yCurve.keyValues[keyIndex] - limb.animTranslation.value.y; - float z = limb.animTranslation.zCurve.keyValues[keyIndex] - limb.animTranslation.value.z; - translations[i] = new Vector3f(x, y, z).divideLocal(unitSize); - } - } else { - for(int i = 0; i < keysCount; ++i) - translations[i] = new Vector3f(); - } - if(haveRotation) { - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKey + i; - float x = limb.animRotation.xCurve.keyValues[keyIndex]; - float y = limb.animRotation.yCurve.keyValues[keyIndex]; - float z = limb.animRotation.zCurve.keyValues[keyIndex]; - rotations[i] = new Quaternion().fromAngles(FastMath.DEG_TO_RAD * x, FastMath.DEG_TO_RAD * y, FastMath.DEG_TO_RAD * z); - } - } else { - for(int i = 0; i < keysCount; ++i) - rotations[i] = new Quaternion(); - } - if(haveScale) { - scales = new Vector3f[keysCount]; - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKey + i; - float x = limb.animScale.xCurve.keyValues[keyIndex]; - float y = limb.animScale.yCurve.keyValues[keyIndex]; - float z = limb.animScale.zCurve.keyValues[keyIndex]; - scales[i] = new Vector3f(x, y, z); - } - } - BoneTrack track = null; - if(haveScale) - track = new BoneTrack(limb.boneIndex, times, translations, rotations, scales); - else - track = new BoneTrack(limb.boneIndex, times, translations, rotations); - anim.addTrack(track); - } - anims.put(anim.getName(), anim); - } - animControl.setAnimations(anims); - } - - private void releaseObjects() { - meshDataMap.clear(); - matDataMap.clear(); - texDataMap.clear(); - imgDataMap.clear(); - modelDataMap.clear(); - poseDataMap.clear(); - skinMap.clear(); - clusterMap.clear(); - acurveMap.clear(); - anodeMap.clear(); - alayerMap.clear(); - refMap.clear(); - propMap.clear(); - modelMap.clear(); - limbMap.clear(); - bindMap.clear(); - geomMap.clear(); - matMap.clear(); - texMap.clear(); - imgMap.clear(); - skeleton = null; - animControl = null; - 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 - } - - 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; - } - } - -} + + private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); + + private AssetManager assetManager; + 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 + + // Loaded objects data + private Map meshDataMap = new HashMap(); + private Map imgDataMap = new HashMap(); // Video clips + private Map modelDataMap = new HashMap(); // Mesh nodes and limb nodes + private Map poseDataMap = new HashMap(); // Node bind poses + private Map skinMap = new HashMap(); // Skin for bone clusters + private Map clusterMap = new HashMap(); // Bone skinning cluster + private Map acurveMap = new HashMap(); // Animation curves + private Map anodeMap = new HashMap(); // Animation nodes + private Map alayerMap = new HashMap(); // Amination layers + private Map> refMap = new HashMap>(); // Object links + private Map> propMap = new HashMap>(); // Property links + + // Scene objects + private Map modelMap = new HashMap(); // Mesh nodes + private Map limbMap = new HashMap(); // Bones + private Map bindMap = new HashMap(); // Node bind poses + private Map geomMap = new HashMap(); // Mesh geometries + private Map imgMap = new HashMap(); + private Skeleton skeleton; + private AnimControl animControl; + + //Loaders + private FbxMaterialLoader matLoader; + private FbxTextureLoader texLoader; + + @Override + public Object load(AssetInfo assetInfo) throws IOException { + this.assetManager = assetInfo.getManager(); + this.initLoaders(); + 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; + try { + sceneFilename = assetKey.getName(); + sceneFolderName = assetKey.getFolder(); + String ext = assetKey.getExtension(); + sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1); + if(sceneFolderName != null && sceneFolderName.length() > 0) + sceneName = sceneName.substring(sceneFolderName.length()); + reset(); + loadScene(stream); + sceneNode = linkScene(); + } finally { + releaseObjects(); + if(stream != null) { + stream.close(); + } + } + return sceneNode; + } + + private void initLoaders() { + matLoader = new FbxMaterialLoader(assetManager); + texLoader = new FbxTextureLoader(); + } + + private void reset() { + unitSize = 1; + animFrameRate = 30; + } + + private void loadScene(InputStream stream) throws IOException { + logger.log(Level.FINE, "Loading scene {0}", sceneFilename); + long startTime = System.currentTimeMillis(); + FbxFile scene = FbxReader.readFBX(stream); + for(FbxElement e : scene.rootElements) { + if(e.id.equals("GlobalSettings")) + loadGlobalSettings(e); + else if(e.id.equals("Objects")) + loadObjects(e); + else if(e.id.equals("Connections")) + loadConnections(e); + } + long estimatedTime = System.currentTimeMillis() - startTime; + logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime); + } + + private void loadGlobalSettings(FbxElement element) { + for(FbxElement e : element.children) { + if(e.id.equals("Properties70")) { + for(FbxElement e2 : e.children) { + if(e2.id.equals("P")) { + String propName = (String) e2.properties.get(0); + if(propName.equals("UnitScaleFactor")) + this.unitSize = ((Double) e2.properties.get(4)).floatValue(); + else if(propName.equals("CustomFrameRate")) { + float framerate = ((Double) e2.properties.get(4)).floatValue(); + if(framerate != -1) + this.animFrameRate = framerate; + } + } + } + } + } + } + + private void loadObjects(FbxElement element) { + for(FbxElement e : element.children) { + if(e.id.equals("Geometry")) + loadGeometry(e); + else if(e.id.equals("Material")) + matLoader.load(e); + else if(e.id.equals("Model")) + loadModel(e); + else if(e.id.equals("Pose")) + loadPose(e); + else if(e.id.equals("Texture")) + texLoader.load(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 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 loadImage(FbxElement element) { + long id = (Long) element.properties.get(0); + String path = (String) element.properties.get(1); + String type = (String) element.properties.get(2); + if(type.equals("Clip")) { + ImageData data = new ImageData(); + data.name = path.substring(0, path.indexOf(0)); + for(FbxElement e : element.children) { + if(e.id.equals("Type")) + data.type = (String) e.properties.get(0); + else if(e.id.equals("FileName")) + data.filename = (String) e.properties.get(0); + else if(e.id.equals("RelativeFilename")) + data.relativeFilename = (String) e.properties.get(0); + else if(e.id.equals("Content")) { + if(e.properties.size() > 0) + data.content = (byte[]) e.properties.get(0); + } + } + imgDataMap.put(id, data); + } + } + + private void loadDeformer(FbxElement element) { + long id = (Long) element.properties.get(0); + String type = (String) element.properties.get(2); + if(type.equals("Skin")) { + SkinData skinData = new SkinData(); + for(FbxElement e : element.children) { + if(e.id.equals("SkinningType")) + skinData.type = (String) e.properties.get(0); + } + skinMap.put(id, skinData); + } else if(type.equals("Cluster")) { + ClusterData clusterData = new ClusterData(); + for(FbxElement e : element.children) { + if(e.id.equals("Indexes")) + clusterData.indexes = (int[]) e.properties.get(0); + else if(e.id.equals("Weights")) + clusterData.weights = (double[]) e.properties.get(0); + else if(e.id.equals("Transform")) + clusterData.transform = (double[]) e.properties.get(0); + else if(e.id.equals("TransformLink")) + clusterData.transformLink = (double[]) e.properties.get(0); + } + clusterMap.put(id, clusterData); + } + } + + private void loadAnimLayer(FbxElement element) { + long id = (Long) element.properties.get(0); + String path = (String) element.properties.get(1); + String type = (String) element.properties.get(2); + if(type.equals("")) { + AnimLayer layer = new AnimLayer(); + layer.name = path.substring(0, path.indexOf(0)); + alayerMap.put(id, layer); + } + } + + private void loadAnimCurve(FbxElement element) { + long id = (Long) element.properties.get(0); + String type = (String) element.properties.get(2); + if(type.equals("")) { + AnimCurveData data = new AnimCurveData(); + for(FbxElement e : element.children) { + if(e.id.equals("KeyTime")) + data.keyTimes = (long[]) e.properties.get(0); + else if(e.id.equals("KeyValueFloat")) + data.keyValues = (float[]) e.properties.get(0); + } + acurveMap.put(id, data); + } + } + + private void loadAnimNode(FbxElement element) { + long id = (Long) element.properties.get(0); + String path = (String) element.properties.get(1); + String type = (String) element.properties.get(2); + if(type.equals("")) { + Double x = null, y = null, z = null; + for(FbxElement e : element.children) { + if(e.id.equals("Properties70")) { + for(FbxElement e2 : e.children) { + if(e2.id.equals("P")) { + String propName = (String) e2.properties.get(0); + if(propName.equals("d|X")) + x = (Double) e2.properties.get(4); + else if(propName.equals("d|Y")) + y = (Double) e2.properties.get(4); + else if(propName.equals("d|Z")) + z = (Double) e2.properties.get(4); + } + } + } + } + // Load only T R S curve nodes + if(x != null && y != null && z != null) { + AnimNode node = new AnimNode(); + node.value = new Vector3f(x.floatValue(), y.floatValue(), z.floatValue()); + node.name = path.substring(0, path.indexOf(0)); + anodeMap.put(id, node); + } + } + } + + private void loadConnections(FbxElement element) { + for(FbxElement e : element.children) { + if(e.id.equals("C")) { + String type = (String) e.properties.get(0); + long objId, refId; + if(type.equals("OO")) { + objId = (Long) e.properties.get(1); + refId = (Long) e.properties.get(2); + List links = refMap.get(objId); + if(links == null) { + links = new ArrayList(); + refMap.put(objId, links); + } + links.add(refId); + } else if(type.equals("OP")) { + objId = (Long) e.properties.get(1); + refId = (Long) e.properties.get(2); + String propName = (String) e.properties.get(3); + List props = propMap.get(objId); + if(props == null) { + props = new ArrayList(); + propMap.put(objId, props); + } + props.add(new PropertyLink(refId, propName)); + } + } + } + } + + private Geometry createGeomerty(MeshData data) { + Mesh mesh = new Mesh(); + mesh.setMode(Mode.Triangles); + // Since each vertex should contain unique texcoord and normal we should unroll vertex indexing + // So we don't use VertexBuffer.Type.Index for elements drawing + // Moreover quads should be triangulated (this increases number of vertices) + boolean isQuads = false; + if(data.indices != null) { + data.iCount = data.indices.length; + data.srcVertexCount = data.vertices.length / 3; + // Indices contains negative numbers to define polygon last index + // Check indices strides to be sure we have triangles or quads + boolean allTriangles = true; + boolean allQads = true; + for(int i = 0; i < data.indices.length; ++i) { + if(i % 3 == 2) { // Triangle stride + if(data.indices[i] >= 0) + allTriangles = false; + } else { + if(data.indices[i] < 0) + allTriangles = false; + } + if(i % 4 == 3) { // Quad stride + if(data.indices[i] >= 0) + allQads = false; + } else { + if(data.indices[i] < 0) + allQads = false; + } + } + if(allTriangles) { + isQuads = false; + data.vCount = data.iCount; + } else if(allQads) { + isQuads = true; + data.vCount = 6 * (data.iCount / 4); // Each quad will be splited into two triangles + } else + throw new AssetLoadException("Unsupported PolygonVertexIndex stride"); + data.vertexMap = new int[data.vCount]; + data.indexMap = new int[data.vCount]; + // Unroll index array into vertex mapping + int n = 0; + for(int i = 0; i < data.iCount; ++i) { + int index = data.indices[i]; + if(index < 0) { + int lastIndex = -(index + 1); + if(isQuads) { + data.vertexMap[n + 0] = data.indices[i - 3]; + data.vertexMap[n + 1] = data.indices[i - 2]; + data.vertexMap[n + 2] = data.indices[i - 1]; + data.vertexMap[n + 3] = data.indices[i - 3]; + data.vertexMap[n + 4] = data.indices[i - 1]; + data.vertexMap[n + 5] = lastIndex; + data.indexMap[n + 0] = (i - 3); + data.indexMap[n + 1] = (i - 2); + data.indexMap[n + 2] = (i - 1); + data.indexMap[n + 3] = (i - 3); + data.indexMap[n + 4] = (i - 1); + data.indexMap[n + 5] = (i - 0); + n += 6; + } else { + data.vertexMap[n + 0] = data.indices[i - 2]; + data.vertexMap[n + 1] = data.indices[i - 1]; + data.vertexMap[n + 2] = lastIndex; + data.indexMap[n + 0] = (i - 2); + data.indexMap[n + 1] = (i - 1); + data.indexMap[n + 2] = (i - 0); + n += 3; + } + } + } + // Build reverse vertex mapping + data.reverseVertexMap = new ArrayList>(data.srcVertexCount); + for(int i = 0; i < data.srcVertexCount; ++i) + data.reverseVertexMap.add(new ArrayList()); + for(int i = 0; i < data.vCount; ++i) { + int index = data.vertexMap[i]; + data.reverseVertexMap.get(index).add(i); + } + } else { + // Stub for no vertex indexing (direct mapping) + data.iCount = data.vCount = data.srcVertexCount; + data.vertexMap = new int[data.vCount]; + data.indexMap = new int[data.vCount]; + data.reverseVertexMap = new ArrayList>(data.vCount); + for(int i = 0; i < data.vCount; ++i) { + data.vertexMap[i] = i; + data.indexMap[i] = i; + List reverseIndices = new ArrayList(1); + reverseIndices.add(i); + data.reverseVertexMap.add(reverseIndices); + } + } + if(data.vertices != null) { + // Unroll vertices data array + FloatBuffer posBuf = BufferUtils.createFloatBuffer(data.vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf); + int srcCount = data.vertices.length / 3; + for(int i = 0; i < data.vCount; ++i) { + int index = data.vertexMap[i]; + if(index > srcCount) + throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount); + float x = (float) data.vertices[3 * index + 0] / unitSize; + float y = (float) data.vertices[3 * index + 1] / unitSize; + float z = (float) data.vertices[3 * index + 2] / unitSize; + posBuf.put(x).put(y).put(z); + } + } + if(data.normals != null) { + // Unroll normals data array + FloatBuffer normBuf = BufferUtils.createFloatBuffer(data.vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf); + int[] mapping = null; + if(data.normalsMapping.equals("ByVertice")) + mapping = data.vertexMap; + else if(data.normalsMapping.equals("ByPolygonVertex")) + mapping = data.indexMap; + int srcCount = data.normals.length / 3; + for(int i = 0; i < data.vCount; ++i) { + int index = mapping[i]; + if(index > srcCount) + throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount); + float x = (float) data.normals[3 * index + 0]; + float y = (float) data.normals[3 * index + 1]; + float z = (float) data.normals[3 * index + 2]; + normBuf.put(x).put(y).put(z); + } + } + if(data.tangents != null) { + // Unroll normals data array + FloatBuffer tanBuf = BufferUtils.createFloatBuffer(data.vCount * 4); + mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tanBuf); + int[] mapping = null; + if(data.tangentsMapping.equals("ByVertice")) + mapping = data.vertexMap; + else if(data.tangentsMapping.equals("ByPolygonVertex")) + mapping = data.indexMap; + int srcCount = data.tangents.length / 3; + for(int i = 0; i < data.vCount; ++i) { + int index = mapping[i]; + if(index > srcCount) + throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount); + float x = (float) data.tangents[3 * index + 0]; + float y = (float) data.tangents[3 * index + 1]; + float z = (float) data.tangents[3 * index + 2]; + tanBuf.put(x).put(y).put(z).put(-1.0f); + } + } + if(data.binormals != null) { + // Unroll normals data array + FloatBuffer binormBuf = BufferUtils.createFloatBuffer(data.vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormBuf); + int[] mapping = null; + if(data.binormalsMapping.equals("ByVertice")) + mapping = data.vertexMap; + else if(data.binormalsMapping.equals("ByPolygonVertex")) + mapping = data.indexMap; + int srcCount = data.binormals.length / 3; + for(int i = 0; i < data.vCount; ++i) { + int index = mapping[i]; + if(index > srcCount) + throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount); + float x = (float) data.binormals[3 * index + 0]; + float y = (float) data.binormals[3 * index + 1]; + float z = (float) data.binormals[3 * index + 2]; + binormBuf.put(x).put(y).put(z); + } + } + if(data.uv != null) { + int[] unIndexMap = data.vertexMap; + if(data.uvIndex != null) { + int uvIndexSrcCount = data.uvIndex.length; + if(uvIndexSrcCount != data.iCount) + throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + data.iCount); + // Unroll UV index array + unIndexMap = new int[data.vCount]; + int n = 0; + for(int i = 0; i < data.iCount; ++i) { + int index = data.uvIndex[i]; + if(isQuads && (i % 4) == 3) { + unIndexMap[n + 0] = data.uvIndex[i - 3]; + unIndexMap[n + 1] = data.uvIndex[i - 1]; + unIndexMap[n + 2] = index; + n += 3; + } else { + unIndexMap[i] = index; + } + } + } + // Unroll UV data array + FloatBuffer tcBuf = BufferUtils.createFloatBuffer(data.vCount * 2); + mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, tcBuf); + int srcCount = data.uv.length / 2; + for(int i = 0; i < data.vCount; ++i) { + int index = unIndexMap[i]; + if(index > srcCount) + throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount); + float u = 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 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 Image createImage(ImageData data) { + Image image = null; + if(data.filename != null) { + // Try load by absolute path + File file = new File(data.filename); + if(file.exists() && file.isFile()) { + File dir = new File(file.getParent()); + String locatorPath = dir.getAbsolutePath(); + Texture tex = null; + try { + assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + tex = assetManager.loadTexture(file.getName()); + } catch(Exception e) { + } finally { + assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + } + if(tex != null) + image = tex.getImage(); + } + } + if(image == null && data.relativeFilename != null) { + // Try load by relative path + File dir = new File(sceneFolderName); + String locatorPath = dir.getAbsolutePath(); + Texture tex = null; + try { + assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + tex = assetManager.loadTexture(data.relativeFilename); + } catch(Exception e) { + } finally { + assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + } + if(tex != null) + image = tex.getImage(); + } + if(image == null && data.content != null) { + // Try load from content + String filename = null; + if(data.filename != null) + filename = new File(data.filename).getName(); + if(filename != null && data.relativeFilename != null) + filename = data.relativeFilename; + // Filename is required to aquire asset loader by extension + if(filename != null) { + String locatorPath = sceneFilename; + filename = sceneFilename + File.separatorChar + filename; // Unique path + Texture tex = null; + try { + assetManager.registerLocator(locatorPath, com.jme3.scene.plugins.fbx.ContentTextureLocator.class); + tex = assetManager.loadTexture(new ContentTextureKey(filename, data.content)); + } catch(Exception e) { + } finally { + assetManager.unregisterLocator(locatorPath, com.jme3.scene.plugins.fbx.ContentTextureLocator.class); + } + if(tex != null) + image = tex.getImage(); + } + } + if(image == null) + throw new AssetLoadException("Content not loaded for image " + data.name); + return image; + } + + private Transform buildTransform(double[] transform) { + float[] m = new float[transform.length]; + for(int i = 0; i < transform.length; ++i) + m[i] = (float) transform[i]; + Matrix4f matrix = new Matrix4f(m); + Vector3f pos = matrix.toTranslationVector(); + Quaternion rot = matrix.toRotationQuat(); + Vector3f scale = matrix.toScaleVector(); + return new Transform(pos, rot, scale); + } + + private Quaternion quatFromBoneAngles(float xAngle, float yAngle, float zAngle) { + float angle; + float sinY, sinZ, sinX, cosY, cosZ, cosX; + angle = zAngle * 0.5f; + sinZ = FastMath.sin(angle); + cosZ = FastMath.cos(angle); + angle = yAngle * 0.5f; + sinY = FastMath.sin(angle); + cosY = FastMath.cos(angle); + angle = xAngle * 0.5f; + sinX = FastMath.sin(angle); + cosX = FastMath.cos(angle); + float cosYXcosZ = cosY * cosZ; + float sinYXsinZ = sinY * sinZ; + float cosYXsinZ = cosY * sinZ; + float sinYXcosZ = sinY * cosZ; + // For some reason bone space is differ, this is modified formulas + float w = (cosYXcosZ * cosX + sinYXsinZ * sinX); + float x = (cosYXcosZ * sinX - sinYXsinZ * cosX); + float y = (sinYXcosZ * cosX + cosYXsinZ * sinX); + float z = (cosYXsinZ * cosX - sinYXcosZ * sinX); + return new Quaternion(x, y, z, w).normalizeLocal(); + } + + private Node linkScene() { + logger.log(Level.FINE, "Linking scene objects"); + long startTime = System.currentTimeMillis(); + Node sceneNode = linkSceneNodes(); + linkMaterials(); + linkMeshes(sceneNode); + linkSkins(sceneNode); + linkAnimations(); + long estimatedTime = System.currentTimeMillis() - startTime; + logger.log(Level.FINE, "Linking done in {0} ms", estimatedTime); + return sceneNode; + } + + private Node linkSceneNodes() { + Node sceneNode = new Node(sceneName + "-scene"); + // Build mesh nodes + for(long nodeId : modelDataMap.keySet()) { + ModelData data = modelDataMap.get(nodeId); + 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 Images + for(long imgId : imgDataMap.keySet()) { + ImageData data = imgDataMap.get(imgId); + Image img = createImage(data); + if(img != null) + imgMap.put(imgId, img); + } + texLoader.linkImagesToTextures(imgMap, refMap); + Map texMap = texLoader.getObjectMap(); + matLoader.linkTexturesToMaterials(texMap, propMap); + } + + 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; + } + } + } + + Map matMap = matLoader.getObjectMap(); + // Link materials to meshes + for(long matId : matMap.keySet()) { + List refs = refMap.get(matId); + if(refs == null) + continue; + Material mat = matMap.get(matId); + for(long refId : refs) { + Node rootNode = modelMap.get(refId); + if(rootNode != null) { + for(Spatial child : rootNode.getChildren()) { + child.setMaterial(mat); + } + } + } + } + } + + private void linkSkins(Node sceneNode) { + // Build skeleton limbs + for(long nodeId : modelDataMap.keySet()) { + ModelData data = modelDataMap.get(nodeId); + if(data.type.equals("LimbNode")) { + Limb limb = createLimb(data); + limbMap.put(nodeId, limb); + } + } + if(limbMap.size() == 0) + return; + // Build skeleton bones + Map bones = new HashMap(); + for(long limbId : limbMap.keySet()) { + Limb limb = limbMap.get(limbId); + Bone bone = new Bone(limb.name); + Transform t = limb.bindTransform; + bone.setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale()); + bones.put(limbId, bone); + } + // Attach bones to roots + for(long limbId : limbMap.keySet()) { + List refs = refMap.get(limbId); + if(refs == null) + continue; + // Find root limb + long rootLimbId = 0L; + for(long refId : refs) { + if(limbMap.containsKey(refId)) { + rootLimbId = refId; + break; + } + } + if(rootLimbId != 0L) { + Bone bone = bones.get(limbId); + Bone root = bones.get(rootLimbId); + root.addChild(bone); + } + } + // Link bone clusters to skin + for(long clusterId : clusterMap.keySet()) { + List refs = refMap.get(clusterId); + if(refs == null) + continue; + for(long skinId : refs) { + if(skinMap.containsKey(skinId)) { + ClusterData data = clusterMap.get(clusterId); + data.skinId = skinId; + break; + } + } + } + // Build the skeleton + this.skeleton = new Skeleton(bones.values().toArray(new Bone[0])); + skeleton.setBindingPose(); + for(long limbId : bones.keySet()) { + Bone bone = bones.get(limbId); + Limb limb = limbMap.get(limbId); + limb.boneIndex = skeleton.getBoneIndex(bone); + } + // Assign bones skinning to meshes + for(long skinId : skinMap.keySet()) { + // Find mesh by skin + Mesh mesh = null; + MeshData meshData = null; + for(long meshId : refMap.get(skinId)) { + Geometry g = geomMap.get(meshId); + if(g != null) { + meshData = meshDataMap.get(meshId); + mesh = g.getMesh(); + break; + } + } + // Bind skinning indexes and weight + if(mesh != null && meshData != null) { + // Create bone buffers + FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(meshData.vCount * 4); + ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(meshData.vCount * 4); + mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeightData); + mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndicesData); + mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(Usage.CpuOnly); + mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(Usage.CpuOnly); + VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex); + indicesHW.setUsage(Usage.CpuOnly); // Setting usage to CpuOnly so that the buffer is not send empty to the GPU + weightsHW.setUsage(Usage.CpuOnly); + mesh.setBuffer(weightsHW); + mesh.setBuffer(indicesHW); + // Accumulate skin bones influence into mesh buffers + boolean bonesLimitExceeded = false; + for(long limbId : bones.keySet()) { + // Search bone cluster for the given limb and skin + ClusterData cluster = null; + for(long clusterId : refMap.get(limbId)) { + ClusterData data = clusterMap.get(clusterId); + if(data != null && data.skinId == skinId) { + cluster = data; + break; + } + } + if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length) + continue; + Limb limb = limbMap.get(limbId); + if(limb.boneIndex > 255) + throw new AssetLoadException("Bone index can't be packed into byte"); + for(int i = 0; i < cluster.indexes.length; ++i) { + int vertexIndex = cluster.indexes[i]; + if(vertexIndex >= meshData.reverseVertexMap.size()) + throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + meshData.reverseVertexMap.size()); + List dstVertices = meshData.reverseVertexMap.get(vertexIndex); + for(int v : dstVertices) { + // Append bone index and weight to vertex + int offset; + float w = 0; + for(offset = v * 4; offset < v * 4 + 4; ++offset) { + w = boneWeightData.get(offset); + if(w == 0) + break; + } + if(w == 0) { + boneWeightData.put(offset, (float) cluster.weights[i]); + boneIndicesData.put(offset, (byte) limb.boneIndex); + } else { + // TODO It would be nice to discard small weight to accumulate more heavy influence + bonesLimitExceeded = true; + } + } + } + } + if(bonesLimitExceeded) + logger.log(Level.WARNING, "Skinning support max 4 bone per vertex. Exceeding data will be discarded."); + // Postprocess bones weights + int maxWeightsPerVert = 0; + boneWeightData.rewind(); + for(int v = 0; v < meshData.vCount; v++) { + float w0 = boneWeightData.get(); + float w1 = boneWeightData.get(); + float w2 = boneWeightData.get(); + float w3 = boneWeightData.get(); + if(w3 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 4); + } else if(w2 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 3); + } else if(w1 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 2); + } else if(w0 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 1); + } + float sum = w0 + w1 + w2 + w3; + if(sum != 1f) { + // normalize weights + float mult = (sum != 0) ? (1f / sum) : 0; + boneWeightData.position(v * 4); + boneWeightData.put(w0 * mult); + boneWeightData.put(w1 * mult); + boneWeightData.put(w2 * mult); + boneWeightData.put(w3 * mult); + } + } + mesh.setMaxNumWeights(maxWeightsPerVert); + mesh.generateBindPose(true); + } + } + // Attach controls + animControl = new AnimControl(skeleton); + sceneNode.addControl(animControl); + SkeletonControl control = new SkeletonControl(skeleton); + sceneNode.addControl(control); + } + + private void linkAnimations() { + if(skeleton == null) + return; + 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.getRef()); + if(anode != null) { + if(prop.getPropName().equals("d|X")) + anode.xCurve = acurve; + else if(prop.getPropName().equals("d|Y")) + anode.yCurve = acurve; + else if(prop.getPropName().equals("d|Z")) + anode.zCurve = acurve; + } + } + } + // Link nodes to layers + for(long nodeId : anodeMap.keySet()) { + List refs = refMap.get(nodeId); + if(refs == null) + continue; + for(long layerId : refs) { + if(alayerMap.containsKey(layerId)) { + AnimNode anode = anodeMap.get(nodeId); + anode.layerId = layerId; + break; + } + } + } + // Extract aminations + HashMap anims = new HashMap(); + for(AnimInverval animInfo : animList.list) { + float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate; + float animStart = animInfo.firstFrame / this.animFrameRate; + float animStop = animInfo.lastFrame / this.animFrameRate; + Animation anim = new Animation(animInfo.name, length); + // Search source layer for animation nodes + long sourceLayerId = 0L; + for(long layerId : alayerMap.keySet()) { + AnimLayer layer = alayerMap.get(layerId); + if(layer.name.equals(animInfo.layerName)) { + sourceLayerId = layerId; + break; + } + } + // Assign animation nodes to limbs + for(Limb limb : limbMap.values()) { + limb.animTranslation = null; + limb.animRotation = null; + limb.animScale = null; + } + for(long nodeId : anodeMap.keySet()) { + List props = propMap.get(nodeId); + if(props == null) + continue; + AnimNode anode = anodeMap.get(nodeId); + if(sourceLayerId != 0L && anode.layerId != sourceLayerId) + continue; // filter node + for(PropertyLink prop : props) { + Limb limb = limbMap.get(prop.getRef()); + if(limb != null) { + if(prop.getPropName().equals("Lcl Translation")) + limb.animTranslation = anode; + else if(prop.getPropName().equals("Lcl Rotation")) + limb.animRotation = anode; + else if(prop.getPropName().equals("Lcl Scaling")) + limb.animScale = anode; + } + } + } + // Build bone tracks + for(Limb limb : limbMap.values()) { + long[] keyTimes = null; + boolean haveTranslation = (limb.animTranslation != null && limb.animTranslation.xCurve != null && limb.animTranslation.yCurve != null && limb.animTranslation.zCurve != null); + boolean haveRotation = (limb.animRotation != null && limb.animRotation.xCurve != null && limb.animRotation.yCurve != null && limb.animRotation.zCurve != null); + boolean haveScale = (limb.animScale != null && limb.animScale.xCurve != null && limb.animScale.yCurve != null && limb.animScale.zCurve != null); + // Search key time array + if(haveTranslation) + keyTimes = limb.animTranslation.xCurve.keyTimes; + else if(haveRotation) + keyTimes = limb.animRotation.xCurve.keyTimes; + else if(haveScale) + keyTimes = limb.animScale.xCurve.keyTimes; + if(keyTimes == null) + continue; + // Calculate keys interval by animation time interval + int firstKey = 0; + int lastKey = keyTimes.length - 1; + for(int i = 0; i < keyTimes.length; ++i) { + float time = (float) (((double) keyTimes[i]) * secondsPerUnit); // Translate into seconds + if(time <= animStart) + firstKey = i; + if(time >= animStop) { + lastKey = i; + break; + } + } + int keysCount = lastKey - firstKey + 1; + if(keysCount <= 0) + continue; + float[] times = new float[keysCount]; + Vector3f[] translations = new Vector3f[keysCount]; + Quaternion[] rotations = new Quaternion[keysCount]; + Vector3f[] scales = null; + // Calculate keyframes times + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKey + i; + float time = (float) (((double) keyTimes[keyIndex]) * secondsPerUnit); // Translate into seconds + times[i] = time - animStart; + } + // Load keyframes from animation curves + if(haveTranslation) { + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKey + i; + float x = limb.animTranslation.xCurve.keyValues[keyIndex] - limb.animTranslation.value.x; + float y = limb.animTranslation.yCurve.keyValues[keyIndex] - limb.animTranslation.value.y; + float z = limb.animTranslation.zCurve.keyValues[keyIndex] - limb.animTranslation.value.z; + translations[i] = new Vector3f(x, y, z).divideLocal(unitSize); + } + } else { + for(int i = 0; i < keysCount; ++i) + translations[i] = new Vector3f(); + } + if(haveRotation) { + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKey + i; + float x = limb.animRotation.xCurve.keyValues[keyIndex]; + float y = limb.animRotation.yCurve.keyValues[keyIndex]; + float z = limb.animRotation.zCurve.keyValues[keyIndex]; + rotations[i] = new Quaternion().fromAngles(FastMath.DEG_TO_RAD * x, FastMath.DEG_TO_RAD * y, FastMath.DEG_TO_RAD * z); + } + } else { + for(int i = 0; i < keysCount; ++i) + rotations[i] = new Quaternion(); + } + if(haveScale) { + scales = new Vector3f[keysCount]; + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKey + i; + float x = limb.animScale.xCurve.keyValues[keyIndex]; + float y = limb.animScale.yCurve.keyValues[keyIndex]; + float z = limb.animScale.zCurve.keyValues[keyIndex]; + scales[i] = new Vector3f(x, y, z); + } + } + BoneTrack track = null; + if(haveScale) + track = new BoneTrack(limb.boneIndex, times, translations, rotations, scales); + else + track = new BoneTrack(limb.boneIndex, times, translations, rotations); + anim.addTrack(track); + } + anims.put(anim.getName(), anim); + } + animControl.setAnimations(anims); + } + + private void releaseObjects() { + meshDataMap.clear(); + imgDataMap.clear(); + modelDataMap.clear(); + poseDataMap.clear(); + skinMap.clear(); + clusterMap.clear(); + acurveMap.clear(); + anodeMap.clear(); + alayerMap.clear(); + refMap.clear(); + propMap.clear(); + modelMap.clear(); + limbMap.clear(); + bindMap.clear(); + geomMap.clear(); + imgMap.clear(); + matLoader.release(); + texLoader.release(); + 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 + } + + 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 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; + } +} \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java index 1a4d09d53a..7c852ccc95 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java @@ -32,10 +32,18 @@ package com.jme3.scene.plugins.fbx.file; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.scene.plugins.fbx.misc.FbxClassTypeDispatcher; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import com.jme3.scene.plugins.fbx.obj.FbxUnknownObject; public class FbxElement { - + private static final Logger logger = Logger.getLogger(FbxElement.class.getName()); + public String id; public List properties; /* @@ -56,7 +64,6 @@ public class FbxElement { */ public char[] propertiesTypes; public List children = new ArrayList(); - public FbxElement(int propsCount) { this.properties = new ArrayList(propsCount); this.propertiesTypes = new char[propsCount]; @@ -117,6 +124,33 @@ public List getFbxProperties() { return props; } + /** + * @return string: the subclassname of this element, as stored in the properties. + */ + public String getSubclassName() { + String subclassName = null; + if (this.propertiesTypes.length == 3) { + // FBX 7.x (all objects start with Long ID) + subclassName = (String) this.properties.get(2); + } else if (this.propertiesTypes.length == 2) { + // FBX 6.x (objects only have name and subclass) + subclassName = (String) this.properties.get(1); + } + return subclassName; + } + + /** + * Resolves the class that belongs to this FbxElement given its properties + */ + public Class resolveFbxClass() { + String subclassName = getSubclassName(); + Class res = FbxClassTypeDispatcher.getInstance().dispatchType(this.id, subclassName); + if(res.equals(FbxUnknownObject.class)) { + logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); + } + return res; + } + @Override public String toString() { return "FBXElement[id=" + id + ", numProps=" + properties.size() + ", numChildren=" + children.size() + "]"; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxElementLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxElementLoader.java new file mode 100644 index 0000000000..5f3ee6d594 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxElementLoader.java @@ -0,0 +1,22 @@ +package com.jme3.scene.plugins.fbx.loaders; + +import java.util.Map; + +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public interface FbxElementLoader { + /** + * Loads an fbxelement in the local datamap + */ + public void load(FbxElement element); + + /** + * Returns all the created instances of this loader + */ + public Map getObjectMap(); + + /** + * Clears all data that was saved in the local map + */ + public void release(); +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxMaterialLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxMaterialLoader.java new file mode 100644 index 0000000000..2c533af2c6 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxMaterialLoader.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2009-2016 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.loaders; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.fbx.loaders.PropertyLink; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.texture.Texture; + +public class FbxMaterialLoader implements FbxElementLoader { + + private AssetManager assetManager; + private Map matDataMap = new HashMap(); + private Map matMap = new HashMap(); + + public FbxMaterialLoader(AssetManager assetMgr) { + assetManager = assetMgr; + } + + public void load(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); + } + } + + /** + * Links textures to materials using the given propertymap + * @param texMap + * @param propMap + */ + public void linkTexturesToMaterials(Map texMap, Map> propMap) { + // Build materials + createMaterials(); + + // Link given textures to materials + 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.getRef()); + if(mat != null) { + if(prop.getPropName().equals("DiffuseColor")) { + mat.setTexture("DiffuseMap", tex); + mat.setColor("Diffuse", ColorRGBA.White); + } else if(prop.getPropName().equals("SpecularColor")) { + mat.setTexture("SpecularMap", tex); + mat.setColor("Specular", ColorRGBA.White); + } else if(prop.getPropName().equals("NormalMap")) + mat.setTexture("NormalMap", tex); + } + } + } + } + + /** + * Creates actual material instances of the MaterialData + */ + private void createMaterials() { + for(long matId : matDataMap.keySet()) { + MaterialData data = matDataMap.get(matId); + Material material = createMaterial(data); + if(material != null) + matMap.put(matId, material); + } + } + + + private Material createMaterial(MaterialData data) { + Material m = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + m.setName(data.name); + data.ambientColor.multLocal(data.ambientFactor); + data.diffuseColor.multLocal(data.diffuseFactor); + data.specularColor.multLocal(data.specularFactor); + m.setColor("Ambient", new ColorRGBA(data.ambientColor.x, data.ambientColor.y, data.ambientColor.z, 1)); + m.setColor("Diffuse", new ColorRGBA(data.diffuseColor.x, data.diffuseColor.y, data.diffuseColor.z, 1)); + m.setColor("Specular", new ColorRGBA(data.specularColor.x, data.specularColor.y, data.specularColor.z, 1)); + m.setFloat("Shininess", data.shininessExponent); + m.setBoolean("UseMaterialColors", true); + m.getAdditionalRenderState().setAlphaTest(true); + m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + return m; + } + + public Map getObjectMap() { + return matMap; + } + + public void release() { + matMap.clear(); + matDataMap.clear(); + } + + public 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxTextureLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxTextureLoader.java new file mode 100644 index 0000000000..8bfc79fe95 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/FbxTextureLoader.java @@ -0,0 +1,78 @@ +package com.jme3.scene.plugins.fbx.loaders; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; + +public class FbxTextureLoader implements FbxElementLoader { + private Map texDataMap = new HashMap(); + private Map texMap = new HashMap(); + + public void load(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); + } + } + + public void linkImagesToTextures(Map imgMap, Map> refMap) { + createTextures(); + + 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); + } + } + } + + public Map getObjectMap() { + return texMap; + } + + public void release() { + texDataMap.clear(); + texMap.clear(); + } + + private void createTextures() { + for(long texId : texDataMap.keySet()) { + TextureData data = texDataMap.get(texId); + Texture tex = createTexture(data); + if(tex != null) + texMap.put(texId, tex); + } + } + + private Texture createTexture(TextureData data) { + Texture tex = new Texture2D(); + tex.setName(data.name); + return tex; + } + + private class TextureData { + String name; + String bindType; + String filename; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/PropertyLink.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/PropertyLink.java new file mode 100644 index 0000000000..af8912e1d3 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/loaders/PropertyLink.java @@ -0,0 +1,19 @@ +package com.jme3.scene.plugins.fbx.loaders; + +public class PropertyLink { + private long ref; + private String propName; + + public PropertyLink(long id, String prop) { + this.ref = id; + this.propName = prop; + } + + public long getRef() { + return ref; + } + + public String getPropName() { + return propName; + } +} \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java index bffd94c658..e24c8ee3dc 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java @@ -152,7 +152,7 @@ public static FbxLayerElement fromPositions(double[] positionData) { layerElement.type = Type.Position; layerElement.mapInfoType = MappingInformationType.ByVertex; layerElement.refInfoType = ReferenceInformationType.Direct; - layerElement.data = toVector3(positionData); + layerElement.data = Vector3f.toVector3(positionData); layerElement.dataIndices = null; return layerElement; } @@ -184,11 +184,11 @@ public static FbxLayerElement fromElement(FbxElement element) { } layerElement.refInfoType = ReferenceInformationType.valueOf(refInfoTypeVal); } else if (child.id.equals("Normals") || child.id.equals("Tangents") || child.id.equals("Binormals")) { - layerElement.data = toVector3(FbxMeshUtil.getDoubleArray(child)); + layerElement.data = Vector3f.toVector3(FbxMeshUtil.getDoubleArray(child)); } else if (child.id.equals("Colors")) { - layerElement.data = toColorRGBA(FbxMeshUtil.getDoubleArray(child)); + layerElement.data = ColorRGBA.toColorRGBA(FbxMeshUtil.getDoubleArray(child)); } else if (child.id.equals("UV")) { - layerElement.data = toVector2(FbxMeshUtil.getDoubleArray(child)); + layerElement.data = Vector2f.toVector2(FbxMeshUtil.getDoubleArray(child)); } else if (indexTypes.contains(child.id)) { layerElement.dataIndices = FbxMeshUtil.getIntArray(child); } else if (child.id.equals("Name")) { @@ -206,38 +206,4 @@ public static FbxLayerElement fromElement(FbxElement element) { } return layerElement; } - - static Vector3f[] toVector3(double[] data) { - Vector3f[] vectors = new Vector3f[data.length / 3]; - for (int i = 0; i < vectors.length; i++) { - float x = (float) data[i * 3]; - float y = (float) data[i * 3 + 1]; - float z = (float) data[i * 3 + 2]; - vectors[i] = new Vector3f(x, y, z); - } - return vectors; - } - - static Vector2f[] toVector2(double[] data) { - Vector2f[] vectors = new Vector2f[data.length / 2]; - for (int i = 0; i < vectors.length; i++) { - float x = (float) data[i * 2]; - float y = (float) data[i * 2 + 1]; - vectors[i] = new Vector2f(x, y); - } - return vectors; - } - - static ColorRGBA[] toColorRGBA(double[] data) { - ColorRGBA[] colors = new ColorRGBA[data.length / 4]; - for (int i = 0; i < colors.length; i++) { - float r = (float) data[i * 4]; - float g = (float) data[i * 4 + 1]; - float b = (float) data[i * 4 + 2]; - float a = (float) data[i * 4 + 3]; - colors[i] = new ColorRGBA(r, g, b, a); - } - return colors; - } -} - +} \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java index 5dd911bed6..30e1599d36 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java @@ -162,7 +162,7 @@ public void connectObjectProperty(FbxObject object, String property) { } private void setPositions(double[] positions) { - this.positions = FbxLayerElement.toVector3(positions); + this.positions = Vector3f.toVector3(positions); } private void setEdges(int[] edges) { diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxClassTypeDispatcher.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxClassTypeDispatcher.java new file mode 100644 index 0000000000..2f8a3a1054 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxClassTypeDispatcher.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2009-2016 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.misc; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import com.jme3.scene.plugins.fbx.obj.FbxUnknownObject; + +/** + * Singleton class for dispatching FbxElements to Fbx-* classes. + * Uses a configuration file in src/fbx/resources for constructing the dispatch table + */ +public class FbxClassTypeDispatcher { + private static final Logger logger = Logger.getLogger(FbxClassTypeDispatcher.class.getName()); + + private static FbxClassTypeDispatcher instance = null; + private final Path configFilePath = Paths.get(System.getProperty("user.dir"), + "src", "fbx", "resources", "FbxImportRules.json"); + + private Map> dispatchTable; + + /** + * @return an existing instance (or a new one if no instance exists) + */ + public static FbxClassTypeDispatcher getInstance() { + if(instance == null) { + instance = new FbxClassTypeDispatcher(); + } + return instance; + } + + /** + * Private constructor. Reads the configuration file as is set in configFilePath and + * converts it to a Map structure. + */ + private FbxClassTypeDispatcher() { + String typesJson = null; + try { + typesJson = new String(Files.readAllBytes(configFilePath)); + } catch (IOException e) { + typesJson = ""; + logger.log(Level.WARNING, "Cannot load configuration for FbxObject parsing. Location: {0}", configFilePath.toString()); + } + Type type = new TypeToken>>(){}.getType(); + dispatchTable = new Gson().fromJson(typesJson, type); + } + + /** + * this method determines which type belongs to the given Strings. + * @param elementName name of the top-level element + * @param subclassName name of the sub-level element + * @return the class corresponding to the given arguments as found in the dispatchTable. + * If no type can be cound, it returns FbxUnknownObject.class + */ + public Class dispatchType(String elementName, String subclassName) { + Map mainType = dispatchTable.get(elementName); + Class res = FbxUnknownObject.class; + + if(mainType == null){ + mainType = dispatchTable.get("*"); + } + + try { + if(mainType.containsKey(subclassName)) { + res = (Class) Class.forName(mainType.get(subclassName)); + } else if (mainType.containsKey("*")) { + res = (Class) Class.forName(mainType.get("*")); + } + } catch (ClassNotFoundException cnfe) { + logger.log(Level.WARNING, "Configuration file contains class that cannot be found.", cnfe); + } + return res; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java index ec8c1fd677..f53a522c70 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java @@ -32,119 +32,16 @@ package com.jme3.scene.plugins.fbx.obj; import com.jme3.asset.AssetManager; -import com.jme3.scene.plugins.fbx.anim.FbxAnimCurve; -import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; -import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer; -import com.jme3.scene.plugins.fbx.anim.FbxAnimStack; -import com.jme3.scene.plugins.fbx.anim.FbxBindPose; -import com.jme3.scene.plugins.fbx.anim.FbxCluster; -import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; -import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer; import com.jme3.scene.plugins.fbx.file.FbxElement; -import com.jme3.scene.plugins.fbx.material.FbxImage; -import com.jme3.scene.plugins.fbx.material.FbxMaterial; -import com.jme3.scene.plugins.fbx.material.FbxTexture; -import com.jme3.scene.plugins.fbx.mesh.FbxMesh; -import com.jme3.scene.plugins.fbx.node.FbxNode; -import com.jme3.scene.plugins.fbx.node.FbxNullAttribute; + import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Responsible for producing FBX objects given an FBXElement. */ public final class FbxObjectFactory { - private static final Logger logger = Logger.getLogger(FbxObjectFactory.class.getName()); - - private static Class getImplementingClass(String elementName, String subclassName) { - if (elementName.equals("NodeAttribute")) { - if (subclassName.equals("Root")) { - // Root of skeleton, may not actually be set. - return FbxNullAttribute.class; - } else if (subclassName.equals("LimbNode")) { - // Specifies some limb attributes, optional. - return FbxNullAttribute.class; - } else if (subclassName.equals("Null")) { - // An "Empty" or "Node" without any specific behavior. - return FbxNullAttribute.class; - } else if (subclassName.equals("IKEffector") || - subclassName.equals("FKEffector")) { - // jME3 does not support IK. - return FbxNullAttribute.class; - } else { - // NodeAttribute - Unknown - logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); - return FbxUnknownObject.class; - } - } else if (elementName.equals("Geometry") && subclassName.equals("Mesh")) { - // NodeAttribute - Mesh Data - return FbxMesh.class; - } else if (elementName.equals("Model")) { - // Scene Graph Node - // Determine specific subclass (e.g. Mesh, Null, or LimbNode?) - if (subclassName.equals("LimbNode")) { - return FbxLimbNode.class; // Child Bone of Skeleton? - } else { - return FbxNode.class; - } - } else if (elementName.equals("Pose")) { - if (subclassName.equals("BindPose")) { - // Bind Pose Information - return FbxBindPose.class; - } else { - // Rest Pose Information - // OR - // Other Data (???) - logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); - return FbxUnknownObject.class; - } - } else if (elementName.equals("Material")) { - return FbxMaterial.class; - } else if (elementName.equals("Deformer")) { - // Deformer - if (subclassName.equals("Skin")) { - // FBXSkinDeformer (mapping between FBXMesh & FBXClusters) - return FbxSkinDeformer.class; - } else if (subclassName.equals("Cluster")) { - // Cluster (aka mapping between FBXMesh vertices & weights for bone) - return FbxCluster.class; - } else { - logger.log(Level.WARNING, "Unknown deformer subclass: {0}. Ignoring.", subclassName); - return FbxUnknownObject.class; - } - } else if (elementName.equals("Video")) { - if (subclassName.equals("Clip")) { - return FbxImage.class; - } else { - logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName); - return FbxUnknownObject.class; - } - } else if (elementName.equals("Texture")) { - return FbxTexture.class; - } else if (elementName.equals("AnimationStack")) { - // AnimationStack (jME Animation) - return FbxAnimStack.class; - } else if (elementName.equals("AnimationLayer")) { - // AnimationLayer (for blended animation - not supported) - return FbxAnimLayer.class; - } else if (elementName.equals("AnimationCurveNode")) { - // AnimationCurveNode - return FbxAnimCurveNode.class; - } else if (elementName.equals("AnimationCurve")) { - // AnimationCurve (Data) - return FbxAnimCurve.class; - } else if (elementName.equals("SceneInfo")) { - // Old-style FBX 6.1 uses this. Nothing useful here. - return FbxUnknownObject.class; - } else { - logger.log(Level.WARNING, "Unknown object class: {0}. Ignoring.", elementName); - return FbxUnknownObject.class; - } - } - /** * Automatically create an FBXObject by inspecting its class / subclass * properties. @@ -156,20 +53,9 @@ private static Class getImplementingClass(String elementNam */ public static FbxObject createObject(FbxElement element, AssetManager assetManager, String sceneFolderName) { String elementName = element.id; - String subclassName; - - if (element.propertiesTypes.length == 3) { - // FBX 7.x (all objects start with Long ID) - subclassName = (String) element.properties.get(2); - } else if (element.propertiesTypes.length == 2) { - // FBX 6.x (objects only have name and subclass) - subclassName = (String) element.properties.get(1); - } else { - // Not an object or invalid data. - return null; - } + String subclassName = element.getSubclassName(); - Class javaFbxClass = getImplementingClass(elementName, subclassName); + Class javaFbxClass = element.resolveFbxClass(); if (javaFbxClass != null) { try { diff --git a/jme3-plugins/src/fbx/resources/FbxImportRules.json b/jme3-plugins/src/fbx/resources/FbxImportRules.json new file mode 100644 index 0000000000..61ef824db0 --- /dev/null +++ b/jme3-plugins/src/fbx/resources/FbxImportRules.json @@ -0,0 +1,50 @@ +{ + "NodeAttribute": { + "Root": "com.jme3.scene.plugins.fbx.node.FbxNullAttribute", + "LimbNode": "com.jme3.scene.plugins.fbx.node.FbxNullAttribute", + "Null": "com.jme3.scene.plugins.fbx.node.FbxNullAttribute", + "IKEffector": "com.jme3.scene.plugins.fbx.node.FbxNullAttribute", + "FKEffector": "com.jme3.scene.plugins.fbx.node.FbxNullAttribute" + }, + "Geometry": { + "Mesh": "com.jme3.scene.plugins.fbx.mesh.FbxMesh" + }, + "Model": { + "LimbNode": "com.jme3.scene.plugins.fbx.anim.FbxLimbNode", + "*": "com.jme3.scene.plugins.fbx.node.FbxNode" + }, + "Pose": { + "BindPose": "com.jme3.scene.plugins.fbx.anim.FbxBindPose" + }, + "Material": { + "*": "com.jme3.scene.plugins.fbx.material.FbxMaterial" + }, + "Deformer": { + "Skin": "com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer", + "Cluster": "com.jme3.scene.plugins.fbx.anim.FbxCluster" + }, + "Video": { + "Clip": "com.jme3.scene.plugins.fbx.material.FbxImage" + }, + "Texture": { + "*": "com.jme3.scene.plugins.fbx.material.FbxTexture" + }, + "AnimationStack": { + "*": "com.jme3.scene.plugins.fbx.anim.FbxAnimStack" + }, + "AnimationCurve": { + "*": "com.jme3.scene.plugins.fbx.anim.FbxAnimCurve" + }, + "AnimationCurveNode": { + "*": "com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode" + }, + "AnimationLayer": { + "*": "com.jme3.scene.plugins.fbx.anim.FbxAnimLayer" + }, + "SceneInfo": { + "*": "com.jme3.scene.plugins.fbx.obj.FbxUnknownObject" + }, + "*": { + "*": "com.jme3.scene.plugins.fbx.obj.FbxUnknownObject" + } +} \ No newline at end of file diff --git a/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/SceneLoaderTest.java b/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/SceneLoaderTest.java new file mode 100644 index 0000000000..197dc17464 --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/SceneLoaderTest.java @@ -0,0 +1,386 @@ +package com.jme3.scene.plugins.fbx; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetManager; +import com.jme3.asset.ModelKey; +import com.jme3.material.MatParam; +import com.jme3.material.Material; +import com.jme3.material.MaterialDef; +import com.jme3.material.RenderState; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +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.scene.plugins.fbx.loaders.FbxMaterialLoader; +import com.jme3.scene.plugins.fbx.loaders.FbxTextureLoader; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.junit.Before; + +@PrepareForTest({FbxReader.class, Node.class, SceneLoader.class, Material.class, FbxMaterialLoader.class, FbxTextureLoader.class, File.class}) +@RunWith(PowerMockRunner.class) +public class SceneLoaderTest { + @Mock + public SceneKey sk; + public ModelKey mk; + public AssetInfo assetInfo; + + private SceneLoader sc; + + @Before + public void setUp() { + sc = new SceneLoader(); + sk = Mockito.mock(SceneKey.class); + mk = Mockito.mock(ModelKey.class); + assetInfo = Mockito.mock(AssetInfo.class); + } + + @Test(expected=NullPointerException.class) + public void testSceneLoaderLoadWithNull() throws IOException { + sc.load(null); + } + + @Test(expected=AssetLoadException.class) + public void testSceneLoaderWithAssetInfoMock() throws IOException { + AssetManager assetMgrMock = Mockito.mock(AssetManager.class); + Mockito.when(assetInfo.getManager()).thenReturn(assetMgrMock); + sc.load(assetInfo); + } + + @Test + public void testSceneLoaderMaterialGetsAttachedToMeshMock() throws Exception { + AssetManager assetMgrMock = Mockito.mock(AssetManager.class); + InputStream inputMock = Mockito.mock(InputStream.class); + MaterialDef materialDefMock = Mockito.mock(MaterialDef.class); + MatParam matParamMock = Mockito.mock(MatParam.class); + + Node n = new Node(); + String childNodeName = "meshChildGetsMaterial"; + n.setName(childNodeName); + Node meshNodeSpied = Mockito.spy(n); + Node sceneNode = new Node(); + String sceneNodeName = "Fr-scene"; + sceneNode.setName(sceneNodeName); + Node rootMeshNode = new Node(); + String rootMeshNodeName = "mesh"; + rootMeshNode.setName(rootMeshNodeName); + Node rootMeshNodeSpied = Mockito.spy(rootMeshNode); + + FbxElement fbxMaterialMock = createElement(3, "Material", new Object[] {100L, "abc\0def", ""}); + FbxElement fbxMeshMock = createElement(3, "Model", new Object[] {200L, "mesh\0ghi", "P"}); + FbxElement fbxMeshChildMock = createElement(3, "Model", new Object[] {300L, "meshChildGetsMaterial\0ghi", "P"}); + FbxElement fbxMaterialLinkMock = createElement(3, "C", new Object[] {"OO", 100L, 200L}); + FbxElement fbxRootNodeLinkMock = createElement(3, "C", new Object[] {"OO", 200L, 0L}); + FbxElement fbxMeshNodeLinkMock = createElement(3, "C", new Object[] {"OO", 300L, 200L}); + + FbxElement fbxConnectionMock = new FbxElement(2); + fbxConnectionMock.id = "Connections"; + fbxConnectionMock.children.add(fbxMaterialLinkMock); + fbxConnectionMock.children.add(fbxRootNodeLinkMock); + fbxConnectionMock.children.add(fbxMeshNodeLinkMock); + + FbxElement fbxElementMock = new FbxElement(2); + fbxElementMock.id = "Objects"; + fbxElementMock.children.add(fbxMaterialMock); + fbxElementMock.children.add(fbxMeshMock); + fbxElementMock.children.add(fbxMeshChildMock); + + FbxFile fbxFileMock = new FbxFile(); + fbxFileMock.rootElements.add(fbxConnectionMock); + fbxFileMock.rootElements.add(fbxElementMock); + + Mockito.when(assetInfo.getManager()).thenReturn(assetMgrMock); + Mockito.when(assetInfo.openStream()).thenReturn(inputMock); + Mockito.when(assetInfo.getKey()).thenReturn(mk); + + Mockito.when(assetMgrMock.loadAsset(Mockito.any(AssetKey.class))).thenReturn(materialDefMock); + + Mockito.when(mk.getName()).thenReturn("Fromage"); + Mockito.when(mk.getExtension()).thenReturn("chee"); + + Mockito.when(materialDefMock.getMaterialParam(Mockito.anyString())).thenReturn(matParamMock); + PowerMockito.mockStatic(FbxReader.class); + PowerMockito.when(FbxReader.readFBX(Mockito.any(InputStream.class))).thenReturn(fbxFileMock); + + //when meshChildGetsMaterial gets instantiated, put the spied object there instead: + // same holds for rootMeshNode + PowerMockito.whenNew(Node.class).withArguments(childNodeName).thenReturn(meshNodeSpied); + PowerMockito.whenNew(Node.class).withArguments(rootMeshNodeName).thenReturn(rootMeshNodeSpied); + PowerMockito.whenNew(Node.class).withArguments(sceneNodeName).thenReturn(sceneNode); + + Node scene = (Node) sc.load(assetInfo); + Spatial mesh = scene.getChild("mesh"); + Spatial meshWithMaterial = ((Node) mesh).getChild("meshChildGetsMaterial"); + assertNotEquals(null, scene.getChild("mesh")); + assertNotEquals(null, meshWithMaterial); + + Mockito.verify(meshNodeSpied, Mockito.times(1)).setMaterial(Mockito.any(Material.class)); + Mockito.verify(rootMeshNodeSpied, Mockito.never()).setMaterial(Mockito.any(Material.class)); + } + + @Test + public void testSceneLoaderNoMaterialNoAttachmentToMesgMock() throws Exception { + AssetManager assetMgrMock = Mockito.mock(AssetManager.class); + InputStream inputMock = Mockito.mock(InputStream.class); + + Node n = new Node(); + String childNodeName = "meshChildNoMaterial"; + n.setName(childNodeName); + Node meshNodeSpied = Mockito.spy(n); + Node sceneNode = new Node(); + String sceneNodeName = "Fr-scene"; + sceneNode.setName(sceneNodeName); + Node rootMeshNode = new Node(); + String rootMeshNodeName = "mesh"; + rootMeshNode.setName(rootMeshNodeName); + Node rootMeshNodeSpied = Mockito.spy(rootMeshNode); + + FbxElement fbxMeshMock = createElement(3, "Model", new Object[] {200L, "mesh\0ghi", "P"}); + FbxElement fbxMeshChildMock = createElement(3, "Model", new Object[] {300L, "meshChildNoMaterial\0ghi", "P"}); + FbxElement fbxRootNodeLinkMock = createElement(3, "C", new Object[] {"OO", 200L, 0L}); + FbxElement fbxMeshNodeLinkMock = createElement(3, "C", new Object[] {"OO", 300L, 200L}); + + FbxElement fbxConnectionMock = new FbxElement(2); + fbxConnectionMock.id = "Connections"; + fbxConnectionMock.children.add(fbxRootNodeLinkMock); + fbxConnectionMock.children.add(fbxMeshNodeLinkMock); + + FbxElement fbxElementMock = new FbxElement(2); + fbxElementMock.id = "Objects"; + fbxElementMock.children.add(fbxMeshMock); + fbxElementMock.children.add(fbxMeshChildMock); + + FbxFile fbxFileMock = new FbxFile(); + fbxFileMock.rootElements.add(fbxConnectionMock); + fbxFileMock.rootElements.add(fbxElementMock); + + Mockito.when(assetInfo.getManager()).thenReturn(assetMgrMock); + Mockito.when(assetInfo.openStream()).thenReturn(inputMock); + Mockito.when(assetInfo.getKey()).thenReturn(mk); + Mockito.when(mk.getName()).thenReturn("Fromage"); + Mockito.when(mk.getExtension()).thenReturn("chee"); + PowerMockito.mockStatic(FbxReader.class); + PowerMockito.when(FbxReader.readFBX(Mockito.any(InputStream.class))).thenReturn(fbxFileMock); + //when meshChildGetsMaterial gets instantiated, put the spied object there instead: + // same holds for rootMeshNode + PowerMockito.whenNew(Node.class).withArguments(childNodeName).thenReturn(meshNodeSpied); + PowerMockito.whenNew(Node.class).withArguments(rootMeshNodeName).thenReturn(rootMeshNodeSpied); + PowerMockito.whenNew(Node.class).withArguments(sceneNodeName).thenReturn(sceneNode); + + Node scene = (Node) sc.load(assetInfo); + Spatial mesh = scene.getChild(rootMeshNodeName); + Spatial meshWithMaterial = ((Node) mesh).getChild(childNodeName); + assertNotEquals(null, scene.getChild(rootMeshNodeName)); + assertNotEquals(null, meshWithMaterial); + + Mockito.verify(meshNodeSpied, Mockito.never()).setMaterial(Mockito.any(Material.class)); + Mockito.verify(rootMeshNodeSpied, Mockito.never()).setMaterial(Mockito.any(Material.class)); + } + + @Test + public void testTextureGetsLinkedToMaterial() throws Exception { + AssetManager assetMgrMock = Mockito.mock(AssetManager.class); + InputStream inputMock = Mockito.mock(InputStream.class); + Material matMock = Mockito.mock(Material.class); + MaterialDef materialDefMock = Mockito.mock(MaterialDef.class); + MatParam matParamMock = Mockito.mock(MatParam.class); + + Node n = new Node(); + String childNodeName = "meshChildGetsMaterial"; + n.setName(childNodeName); + Node meshNodeSpied = Mockito.spy(n); + Node sceneNode = new Node(); + String sceneNodeName = "Fr-scene"; + sceneNode.setName(sceneNodeName); + Node rootMeshNode = new Node(); + String rootMeshNodeName = "mesh"; + rootMeshNode.setName(rootMeshNodeName); + Node rootMeshNodeSpied = Mockito.spy(rootMeshNode); + + String texModifier = "DiffuseColor"; + + FbxElement fbxTextureMock = createElement(3, "Texture", new Object[] {500L, "mytex\0abc", ""}); + FbxElement fbxMaterialMock = createElement(3, "Material", new Object[] {100L, "abc\0def", ""}); + FbxElement fbxMeshMock = createElement(3, "Model", new Object[] {200L, "mesh\0ghi", "P"}); + FbxElement fbxMeshChildMock = createElement(3, "Model", new Object[] {300L, "meshChildGetsMaterial\0ghi", "P"}); + FbxElement fbxTextureMaterialLinkMock = createElement(4, "C", new Object[] {"OP", 500L, 100L, texModifier }); + FbxElement fbxMaterialLinkMock = createElement(3, "C", new Object[] {"OO", 100L, 200L}); + FbxElement fbxRootNodeLinkMock = createElement(3, "C", new Object[] {"OO", 200L, 0L}); + FbxElement fbxMeshNodeLinkMock = createElement(3, "C", new Object[] {"OO", 300L, 200L}); + + FbxElement fbxConnectionMock = new FbxElement(2); + fbxConnectionMock.id = "Connections"; + fbxConnectionMock.children.add(fbxMaterialLinkMock); + fbxConnectionMock.children.add(fbxRootNodeLinkMock); + fbxConnectionMock.children.add(fbxMeshNodeLinkMock); + fbxConnectionMock.children.add(fbxTextureMaterialLinkMock); + + FbxElement fbxElementMock = new FbxElement(2); + fbxElementMock.id = "Objects"; + fbxElementMock.children.add(fbxTextureMock); + fbxElementMock.children.add(fbxMaterialMock); + fbxElementMock.children.add(fbxMeshMock); + fbxElementMock.children.add(fbxMeshChildMock); + + FbxFile fbxFileMock = new FbxFile(); + fbxFileMock.rootElements.add(fbxConnectionMock); + fbxFileMock.rootElements.add(fbxElementMock); + + Mockito.when(assetInfo.getManager()).thenReturn(assetMgrMock); + Mockito.when(assetInfo.openStream()).thenReturn(inputMock); + Mockito.when(assetInfo.getKey()).thenReturn(mk); + + Mockito.when(mk.getName()).thenReturn("Fromage"); + Mockito.when(mk.getExtension()).thenReturn("chee"); + + Mockito.when(assetMgrMock.loadAsset(Mockito.any(AssetKey.class))).thenReturn(materialDefMock); + Mockito.when(materialDefMock.getMaterialParam(Mockito.anyString())).thenReturn(matParamMock); + Mockito.when(matMock.getAdditionalRenderState()).thenReturn(Mockito.mock(RenderState.class)); + + PowerMockito.mockStatic(FbxReader.class); + PowerMockito.when(FbxReader.readFBX(Mockito.any(InputStream.class))).thenReturn(fbxFileMock); + //when meshChildGetsMaterial gets instantiated, put the spied object there instead: + // same holds for rootMeshNode + PowerMockito.whenNew(Node.class).withArguments(childNodeName).thenReturn(meshNodeSpied); + PowerMockito.whenNew(Node.class).withArguments(rootMeshNodeName).thenReturn(rootMeshNodeSpied); + PowerMockito.whenNew(Node.class).withArguments(sceneNodeName).thenReturn(sceneNode); + PowerMockito.whenNew(Material.class).withAnyArguments().thenReturn(matMock); + + Node scene = (Node) sc.load(assetInfo); + Spatial mesh = scene.getChild("mesh"); + Spatial meshWithMaterial = ((Node) mesh).getChild("meshChildGetsMaterial"); + assertNotEquals(null, scene.getChild("mesh")); + assertNotEquals(null, meshWithMaterial); + + Mockito.verify(meshNodeSpied, Mockito.times(1)).setMaterial(Mockito.any(Material.class)); + Mockito.verify(rootMeshNodeSpied, Mockito.never()).setMaterial(Mockito.any(Material.class)); + Mockito.verify(matMock, Mockito.times(1)).setTexture(Mockito.anyString(), Mockito.any(Texture.class)); + } + + @Test + public void testImagesAreLinkedToTextures() throws Exception { + AssetManager assetMgrMock = Mockito.mock(AssetManager.class); + InputStream inputMock = Mockito.mock(InputStream.class); + Material matMock = Mockito.mock(Material.class); + Texture getTexMock = Mockito.mock(Texture.class); + Texture2D setTexMock = Mockito.mock(Texture2D.class); + Image imageMock = Mockito.mock(Image.class); + MaterialDef materialDefMock = Mockito.mock(MaterialDef.class); + MatParam matParamMock = Mockito.mock(MatParam.class); + File fileMock = Mockito.mock(File.class); + + Node n = new Node(); + String childNodeName = "meshChildGetsMaterial"; + n.setName(childNodeName); + Node meshNodeSpied = Mockito.spy(n); + Node sceneNode = new Node(); + String sceneNodeName = "Fr-scene"; + sceneNode.setName(sceneNodeName); + Node rootMeshNode = new Node(); + String rootMeshNodeName = "mesh"; + rootMeshNode.setName(rootMeshNodeName); + Node rootMeshNodeSpied = Mockito.spy(rootMeshNode); + + String texModifier = "DiffuseColor"; + + FbxElement fbxImageMock = createElement(3, "Video", new Object[] {700L, "myimg\0abc", "Clip"}); + fbxImageMock.children.add(createElement(2, "FileName", new Object[] { "kaas", "fromage" })); + FbxElement fbxTextureMock = createElement(3, "Texture", new Object[] {500L, "mytex\0abc", ""}); + FbxElement fbxMaterialMock = createElement(3, "Material", new Object[] {100L, "abc\0def", ""}); + FbxElement fbxMeshMock = createElement(3, "Model", new Object[] {200L, "mesh\0ghi", "P"}); + FbxElement fbxMeshChildMock = createElement(3, "Model", new Object[] {300L, "meshChildGetsMaterial\0ghi", "P"}); + FbxElement fbxImageTextureLinkMock = createElement(3, "C", new Object[] {"OO", 700L, 500L }); + FbxElement fbxTextureMaterialLinkMock = createElement(4, "C", new Object[] {"OP", 500L, 100L, texModifier }); + FbxElement fbxMaterialLinkMock = createElement(3, "C", new Object[] {"OO", 100L, 200L}); + FbxElement fbxRootNodeLinkMock = createElement(3, "C", new Object[] {"OO", 200L, 0L}); + FbxElement fbxMeshNodeLinkMock = createElement(3, "C", new Object[] {"OO", 300L, 200L}); + + FbxElement fbxConnectionMock = new FbxElement(2); + fbxConnectionMock.id = "Connections"; + fbxConnectionMock.children.add(fbxMaterialLinkMock); + fbxConnectionMock.children.add(fbxRootNodeLinkMock); + fbxConnectionMock.children.add(fbxMeshNodeLinkMock); + fbxConnectionMock.children.add(fbxTextureMaterialLinkMock); + fbxConnectionMock.children.add(fbxImageTextureLinkMock); + + FbxElement fbxElementMock = new FbxElement(2); + fbxElementMock.id = "Objects"; + fbxElementMock.children.add(fbxTextureMock); + fbxElementMock.children.add(fbxMaterialMock); + fbxElementMock.children.add(fbxMeshMock); + fbxElementMock.children.add(fbxMeshChildMock); + fbxElementMock.children.add(fbxImageMock); + + FbxFile fbxFileMock = new FbxFile(); + fbxFileMock.rootElements.add(fbxConnectionMock); + fbxFileMock.rootElements.add(fbxElementMock); + + Mockito.when(assetInfo.getManager()).thenReturn(assetMgrMock); + Mockito.when(assetInfo.openStream()).thenReturn(inputMock); + Mockito.when(assetInfo.getKey()).thenReturn(mk); + + Mockito.when(mk.getName()).thenReturn("Fromage"); + Mockito.when(mk.getExtension()).thenReturn("chee"); + + Mockito.when(fileMock.exists()).thenReturn(true); + Mockito.when(fileMock.isFile()).thenReturn(true); + Mockito.when(fileMock.getParent()).thenReturn("abc"); + + Mockito.when(assetMgrMock.loadAsset(Mockito.any(AssetKey.class))).thenReturn(materialDefMock); + Mockito.when(assetMgrMock.loadTexture(Mockito.anyString())).thenReturn(getTexMock); + + Mockito.when(getTexMock.getImage()).thenReturn(imageMock); + Mockito.when(materialDefMock.getMaterialParam(Mockito.anyString())).thenReturn(matParamMock); + Mockito.when(matMock.getAdditionalRenderState()).thenReturn(Mockito.mock(RenderState.class)); + + PowerMockito.mockStatic(FbxReader.class); + PowerMockito.when(FbxReader.readFBX(Mockito.any(InputStream.class))).thenReturn(fbxFileMock); + //when meshChildGetsMaterial gets instantiated, put the spied object there instead: + // same holds for rootMeshNode + PowerMockito.whenNew(Node.class).withArguments(childNodeName).thenReturn(meshNodeSpied); + PowerMockito.whenNew(Node.class).withArguments(rootMeshNodeName).thenReturn(rootMeshNodeSpied); + PowerMockito.whenNew(Node.class).withArguments(sceneNodeName).thenReturn(sceneNode); + PowerMockito.whenNew(Material.class).withAnyArguments().thenReturn(matMock); + PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(fileMock); + PowerMockito.whenNew(Texture.class).withAnyArguments().thenReturn(setTexMock); + PowerMockito.whenNew(Texture2D.class).withAnyArguments().thenReturn(setTexMock); + + Node scene = (Node) sc.load(assetInfo); + Spatial mesh = scene.getChild("mesh"); + Spatial meshWithMaterial = ((Node) mesh).getChild("meshChildGetsMaterial"); + assertNotEquals(null, scene.getChild("mesh")); + assertNotEquals(null, meshWithMaterial); + + Mockito.verify(meshNodeSpied, Mockito.times(1)).setMaterial(Mockito.any(Material.class)); + Mockito.verify(rootMeshNodeSpied, Mockito.never()).setMaterial(Mockito.any(Material.class)); + Mockito.verify(matMock, Mockito.times(1)).setTexture(Mockito.anyString(), Mockito.any(Texture.class)); + Mockito.verify(getTexMock, Mockito.times(1)).getImage(); + Mockito.verify(setTexMock, Mockito.times(1)).setImage(Mockito.any(Image.class)); + } + + private FbxElement createElement(int length, String id, Object[] props) { + FbxElement el = new FbxElement(length); + el.id = id; + for(int i = 0; i < length; i++) + el.properties.add(props[i]); + return el; + } +} diff --git a/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/file/FbxElementTest.java b/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/file/FbxElementTest.java new file mode 100644 index 0000000000..978b01dd6f --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/file/FbxElementTest.java @@ -0,0 +1,40 @@ +package com.jme3.scene.plugins.fbx.file; + +import org.junit.Test; +import static org.junit.Assert.*; + +import java.util.ArrayList; + +public class FbxElementTest { + + @Test + public void testGetSubclassNameTwoProperties() { + FbxElement el = new FbxElement(2); + el.properties = new ArrayList(); + el.properties.add("kaas"); + el.properties.add("fromage"); + el.properties.add("cheese"); + assertEquals("fromage", el.getSubclassName()); + } + + @Test + public void testGetSubclassNameThreeProperties() { + FbxElement el = new FbxElement(3); + el.properties = new ArrayList(); + el.properties.add("kaas"); + el.properties.add("fromage"); + el.properties.add("cheese"); + assertEquals("cheese", el.getSubclassName()); + } + + @Test + public void testGetSubclassNameInvalidAmount() { + FbxElement el = new FbxElement(5); + el.properties = new ArrayList(); + el.properties.add("kaas"); + el.properties.add("fromage"); + el.properties.add("cheese"); + assertEquals(null, el.getSubclassName()); + } + +} diff --git a/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactoryTest.java b/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactoryTest.java new file mode 100644 index 0000000000..8fe4f39c2a --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactoryTest.java @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2009-2015 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.obj; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.plugins.fbx.anim.FbxAnimCurve; +import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; +import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer; +import com.jme3.scene.plugins.fbx.anim.FbxAnimStack; +import com.jme3.scene.plugins.fbx.anim.FbxBindPose; +import com.jme3.scene.plugins.fbx.anim.FbxCluster; +import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; +import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.material.FbxImage; +import com.jme3.scene.plugins.fbx.material.FbxMaterial; +import com.jme3.scene.plugins.fbx.material.FbxTexture; +import com.jme3.scene.plugins.fbx.mesh.FbxMesh; +import com.jme3.scene.plugins.fbx.node.FbxNode; +import com.jme3.scene.plugins.fbx.node.FbxNullAttribute; + +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.junit.Assert.*; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.rmi.activation.UnknownObjectException; +import java.util.ArrayList; + +@RunWith(MockitoJUnitRunner.class) +public class FbxObjectFactoryTest { + + @Mock + private AssetManager assetManager; + + private String sceneFolderName = "this-is-a-scenefolder-path"; + + @Test(expected=NullPointerException.class) + public void testNull() { + FbxObjectFactory.createObject(null, assetManager, sceneFolderName); + } + + @Test + public void testWithNodeAttributeClassTwoAttributes() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("Root"); + mockedEl.id = "NodeAttribute"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxNullAttribute); + } + + @Test + public void testWithNodeAttributeClassThreeAttributes() { + FbxElement mockedEl = new FbxElement(3); + mockedEl.properties.add(12345L); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("Root"); + mockedEl.id = "NodeAttribute"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxNullAttribute); + } + + @Test(expected=UnsupportedOperationException.class) + public void testWithNodeAttributeClassInvalidAmountOfAttributes() { + FbxElement mockedEl = new FbxElement(5); + mockedEl.properties.add(12345L); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("Root"); + mockedEl.properties.add("Cheese"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "NodeAttribute"; + + FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + } + + @Test + public void testWithClassTwoAttributes() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("Root"); + mockedEl.id = "NodeAttribute"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxNullAttribute); + } + + @Test + public void testWithNonExistingIdAndSubclass() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Cheese"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "Cheese"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxUnknownObject); + } + + @Test + public void testWithNodeAttributeAndLimbNode() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("LimbNode"); + mockedEl.id = "NodeAttribute"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxNullAttribute); + } + + @Test + public void testWithNodeAttributeAndNull() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("Null"); + mockedEl.id = "NodeAttribute"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxNullAttribute); + } + + @Test + public void testWithNodeAttributeAndIKEffector() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("IKEffector"); + mockedEl.id = "NodeAttribute"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxNullAttribute); + } + + @Test + public void testWithNodeAttributeAndFKEffector() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("FKEffector"); + mockedEl.id = "NodeAttribute"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxNullAttribute); + } + + @Test + public void testWithNodeAttributeAndRandom() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001NodeAttribute"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "NodeAttribute"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxUnknownObject); + } + + @Test + public void testWithGeometryAndMesh() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Geometry"); + mockedEl.properties.add("Mesh"); + mockedEl.id = "Geometry"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxMesh); + } + + @Test + public void testWithGeometryAndRandom() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Geometry"); + mockedEl.properties.add("Random"); + mockedEl.id = "Geometry"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxUnknownObject); + } + + @Test + public void testWithRandomAndMesh() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Fromage"); + mockedEl.properties.add("Mesh"); + mockedEl.id = "Fromage"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxUnknownObject); + } + + + @Test + public void testWithModelAndLimbNode() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Model"); + mockedEl.properties.add("LimbNode"); + mockedEl.id = "Model"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxLimbNode); + } + + @Test + public void testWithModelAndSomethingElse() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Model"); + mockedEl.properties.add("Cheese"); + mockedEl.id = "Model"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxNode); + } + + @Test + public void testWithPoseAndBindPose() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Pose"); + mockedEl.properties.add("BindPose"); + mockedEl.id = "Pose"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxBindPose); + } + + @Test + public void testWithPoseAndSomethingElse() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Pose"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "Pose"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxUnknownObject); + } + + @Test + public void testWithMaterialAndRandom() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Material"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "Material"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxMaterial); + } + + @Test + public void testWithDeformerAndSkin() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Deformer"); + mockedEl.properties.add("Skin"); + mockedEl.id = "Deformer"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxSkinDeformer); + } + + @Test + public void testWithDeformerAndCluster() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Deformer"); + mockedEl.properties.add("Cluster"); + mockedEl.id = "Deformer"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxCluster); + } + + @Test + public void testWithDeformerAndRandom() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Deformer"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "Deformer"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxUnknownObject); + } + + @Test + public void testWithVideoAndClip() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Video"); + mockedEl.properties.add("Clip"); + mockedEl.id = "Video"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxImage); + } + + @Test + public void testWithVideoAndRandom() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Video"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "Video"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxUnknownObject); + } + + @Test + public void testWithTexture() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001Texture"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "Texture"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxTexture); + } + + @Test + public void testWithAnimationStack() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001AnimationStack"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "AnimationStack"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxAnimStack); + } + + @Test + public void testWithAnimationLayer() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001AnimationLayer"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "AnimationLayer"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxAnimLayer); + } + + @Test + public void testWithAnimationCurveNode() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001AnimationCurveNode"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "AnimationCurveNode"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxAnimCurveNode); + } + + @Test + public void testWithAnimationCurve() { + FbxElement mockedEl = new FbxElement(2); + mockedEl.properties.add("123\u0000\u0001AnimationCurve"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "AnimationCurve"; + + FbxElement mockedChild = new FbxElement(3); + long[] l = {1L,2L}; + mockedChild.properties.add(l); + mockedChild.properties.add("123\u0000\u0001KeyTime"); + mockedChild.properties.add("Fromage"); + mockedChild.id = "KeyTime"; + + mockedEl.children.add(mockedChild); + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + System.out.println(res); + assertTrue(res instanceof FbxAnimCurve); + } + + @Test + public void testWithSceneInfo() { + FbxElement mockedEl = new FbxElement(3); + long[] l = {1L,2L}; + mockedEl.properties.add(l); + mockedEl.properties.add("123\u0000\u0001SceneInfo"); + mockedEl.properties.add("Fromage"); + mockedEl.id = "SceneInfo"; + + FbxObject res = FbxObjectFactory.createObject(mockedEl, assetManager, sceneFolderName); + assertTrue(res instanceof FbxUnknownObject); + } + +}