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); + } + +}