From de8f295615c4efa4b23dd6e4d14ecaaef5a67c98 Mon Sep 17 00:00:00 2001 From: Eirenliel Allier Date: Mon, 2 Jan 2017 23:33:14 +0300 Subject: [PATCH 1/3] Added support of FBX 2016-17 (7.5) --- .../scene/plugins/fbx/file/FbxReader.java | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java index 22e93d8db0..d4af8505cd 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java @@ -41,15 +41,18 @@ import java.util.zip.InflaterInputStream; public class FbxReader { - + public static final int BLOCK_SENTINEL_LENGTH = 13; + public static final int BLOCK_SENTINEL_LENGTH2 = 25; public static final byte[] BLOCK_SENTINEL_DATA = new byte[BLOCK_SENTINEL_LENGTH]; + public static final byte[] BLOCK_SENTINEL_DATA2 = new byte[BLOCK_SENTINEL_LENGTH2]; /** * Majic string at start: * "Kaydara FBX Binary\x20\x20\x00\x1a\x00" */ public static final byte[] HEAD_MAGIC = new byte[]{0x4b, 0x61, 0x79, 0x64, 0x61, 0x72, 0x61, 0x20, 0x46, 0x42, 0x58, 0x20, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x20, 0x00, 0x1a, 0x00}; - + public static final int NEW_FORMAT = 7500; + public static FbxFile readFBX(InputStream stream) throws IOException { FbxFile fbxFile = new FbxFile(); // Read file to byte buffer so we can know current position in file @@ -61,27 +64,27 @@ public static FbxFile readFBX(InputStream stream) throws IOException { // Check majic header byte[] majic = getBytes(byteBuffer, HEAD_MAGIC.length); if(!Arrays.equals(HEAD_MAGIC, majic)) - throw new IOException("Either ASCII FBX or corrupt file. " - + "Only binary FBX files are supported"); - + throw new IOException("Not FBX file"); // Read version fbxFile.version = getUInt(byteBuffer); - // Read root elements while(true) { - FbxElement e = readFBXElement(byteBuffer); + FbxElement e = readFBXElement(byteBuffer, fbxFile); if(e == null) break; fbxFile.rootElements.add(e); } return fbxFile; } - - private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOException { - long endOffset = getUInt(byteBuffer); + + private static FbxElement readFBXElement(ByteBuffer byteBuffer, FbxFile file) throws IOException { + long endOffset = file.version >= NEW_FORMAT ? getULong(byteBuffer) : getUInt(byteBuffer); if(endOffset == 0) return null; - long propCount = getUInt(byteBuffer); - getUInt(byteBuffer); // Properties length unused + long propCount = file.version >= NEW_FORMAT ? getULong(byteBuffer) : getUInt(byteBuffer); + if(file.version >= NEW_FORMAT) + getULong(byteBuffer); + else + getUInt(byteBuffer); // Properties length unused FbxElement element = new FbxElement((int) propCount); element.id = new String(getBytes(byteBuffer, getUByte(byteBuffer))); @@ -92,11 +95,16 @@ private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOExcepti element.propertiesTypes[i] = dataType; } if(byteBuffer.position() < endOffset) { - while(byteBuffer.position() < (endOffset - BLOCK_SENTINEL_LENGTH)) - element.children.add(readFBXElement(byteBuffer)); + while(byteBuffer.position() < (endOffset - (file.version >= NEW_FORMAT ? BLOCK_SENTINEL_LENGTH2 : BLOCK_SENTINEL_LENGTH))) + element.children.add(readFBXElement(byteBuffer, file)); - if(!Arrays.equals(BLOCK_SENTINEL_DATA, getBytes(byteBuffer, BLOCK_SENTINEL_LENGTH))) - throw new IOException("Failed to read block sentinel, expected 13 zero bytes"); + if(file.version >= NEW_FORMAT) { + if(!Arrays.equals(BLOCK_SENTINEL_DATA2, getBytes(byteBuffer, BLOCK_SENTINEL_LENGTH2))) + throw new IOException("Failed to read block sentinel, expected 25 zero bytes"); + } else { + if(!Arrays.equals(BLOCK_SENTINEL_DATA, getBytes(byteBuffer, BLOCK_SENTINEL_LENGTH))) + throw new IOException("Failed to read block sentinel, expected 13 zero bytes"); + } } if(byteBuffer.position() != endOffset) throw new IOException("Data length not equal to expected"); @@ -203,6 +211,15 @@ private static long getUInt(ByteBuffer byteBuffer) { return byteBuffer.getInt() & 0x00000000ffffffffL; } + /** + * Not really UNSIGNED + * @param byteBuffer + * @return + */ + private static long getULong(ByteBuffer byteBuffer) { + return byteBuffer.getLong(); + } + private static int getUByte(ByteBuffer byteBuffer) { return byteBuffer.get() & 0xFF; } @@ -215,13 +232,18 @@ private static byte[] getBytes(ByteBuffer byteBuffer, int size) { private static ByteBuffer readToByteBuffer(InputStream input) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(2048); + byte[] tmp = new byte[2048]; + while(true) { int r = input.read(tmp); if(r == -1) break; + out.write(tmp, 0, r); } - return ByteBuffer.wrap(out.toByteArray()).order(ByteOrder.LITTLE_ENDIAN); + + ByteBuffer bb = ByteBuffer.wrap(out.toByteArray()); + return bb.order(ByteOrder.LITTLE_ENDIAN); } } From d122c0d30e0c4c71a334fc3d1559355c70d119d6 Mon Sep 17 00:00:00 2001 From: Eirenliel Allier Date: Tue, 3 Jan 2017 00:07:23 +0300 Subject: [PATCH 2/3] Updated FBX SceneLoader - Fixed rotation orders (reverse the order, use only for Lcl Rotation) - Fixed animations transformation (proper conversion from parent space to bone space, proper use of rotation order and other transforms) - Implemented Segment Scale Compensate property from Maya (most cases) aka InheritType - Implemented loading bind pose from frame 0 - Properly load all animations from animation layers - Reworked units size (still no program uses it properly to test) --- .../scene/plugins/fbx/ContentTextureKey.java | 1 - .../plugins/fbx/FBXLoadingException.java | 26 ++ .../jme3/scene/plugins/fbx/InheritType.java | 13 + .../jme3/scene/plugins/fbx/RotationOrder.java | 65 +++- .../jme3/scene/plugins/fbx/SceneLoader.java | 314 +++++++++++------- .../plugins/fbx/objects/FbxAnimCurve.java | 22 +- .../plugins/fbx/objects/FbxAnimNode.java | 19 +- .../fbx/objects/FbxAnimationLayer.java | 18 + .../fbx/objects/FbxAnimationStack.java | 25 ++ .../plugins/fbx/objects/FbxBindPose.java | 4 +- .../plugins/fbx/objects/FbxMaterial.java | 4 +- .../scene/plugins/fbx/objects/FbxMesh.java | 92 ++--- .../scene/plugins/fbx/objects/FbxNode.java | 118 +++++-- 13 files changed, 503 insertions(+), 218 deletions(-) create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FBXLoadingException.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/InheritType.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimationLayer.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimationStack.java diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java index 35375f9c59..2db439e401 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java @@ -33,7 +33,6 @@ import com.jme3.asset.TextureKey; import com.jme3.asset.cache.AssetCache; -import com.jme3.asset.cache.WeakRefCloneAssetCache; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FBXLoadingException.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FBXLoadingException.java new file mode 100644 index 0000000000..c8091bea3f --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FBXLoadingException.java @@ -0,0 +1,26 @@ +package com.jme3.scene.plugins.fbx; + +public class FBXLoadingException extends RuntimeException { + + private static final long serialVersionUID = -1641318378648889782L; + + public FBXLoadingException() { + } + + public FBXLoadingException(String message) { + super(message); + } + + public FBXLoadingException(Throwable cause) { + super(cause); + } + + public FBXLoadingException(String message, Throwable cause) { + super(message, cause); + } + + public FBXLoadingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/InheritType.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/InheritType.java new file mode 100644 index 0000000000..82c341a09d --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/InheritType.java @@ -0,0 +1,13 @@ +package com.jme3.scene.plugins.fbx; + +public enum InheritType { + + RrSs, RSrs, + /** + *

Segment Scale Compensate + *

Resets scale of node to (1.0, 1.0, 1.0) + */ + Rrs; + + public static final InheritType[] values = values(); +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java index 8a1e05b361..72e8bc3bb6 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java @@ -1,6 +1,7 @@ package com.jme3.scene.plugins.fbx; import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; @@ -8,7 +9,6 @@ public enum RotationOrder { EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ; - // Static values field for fast access by an oridinal without Enum.values() overhead public static final RotationOrder[] values = values(); private RotationOrder() { @@ -22,6 +22,14 @@ public Quaternion rotate(float x, float y, float z) { return fromEuler(x * FastMath.DEG_TO_RAD, y * FastMath.DEG_TO_RAD, z * FastMath.DEG_TO_RAD, this); } + public Matrix4f rotateToMatrix(Vector3f vec) { + return fromEulerToMatrix(vec.x * FastMath.DEG_TO_RAD, vec.y * FastMath.DEG_TO_RAD, vec.z * FastMath.DEG_TO_RAD, this); + } + + public Matrix4f rotateToMatrix(float x, float y, float z) { + return fromEulerToMatrix(x * FastMath.DEG_TO_RAD, y * FastMath.DEG_TO_RAD, z * FastMath.DEG_TO_RAD, this); + } + private static Quaternion fromEuler(float x, float y, float z, RotationOrder order) { switch(order) { case EULER_XYZ: @@ -42,11 +50,62 @@ private static Quaternion fromEuler(float x, float y, float z, RotationOrder ord } } + private static Matrix4f fromEulerToMatrix(float x, float y, float z, RotationOrder order) { + Matrix4f c = new Matrix4f(); + switch(order) { + case EULER_XYZ: + return rotationZ(z, null).multLocal(rotationY(y, c)).multLocal(rotationX(x, c)); + case EULER_YXZ: + return rotationZ(z, null).multLocal(rotationX(x, c)).multLocal(rotationY(y, c)); + case EULER_ZXY: + return rotationY(y, null).multLocal(rotationX(x, c)).multLocal(rotationZ(z, c)); + case EULER_ZYX: + return rotationX(x, null).multLocal(rotationY(y, c)).multLocal(rotationZ(z, c)); + case EULER_YZX: + return rotationX(x, null).multLocal(rotationZ(z, c)).multLocal(rotationY(y, c)); + case EULER_XZY: + return rotationY(y, null).multLocal(rotationZ(z, c)).multLocal(rotationX(x, c)); + case SPHERIC_XYZ: + default: + throw new IllegalArgumentException("Spheric rotation is unsupported in this importer"); + } + } + private static Quaternion toQuat(float ax1v, Vector3f ax1, float ax2v, Vector3f ax2, float ax3v, Vector3f ax3) { - // TODO It has some potential in optimization + // TODO It has some potential for optimization Quaternion q1 = new Quaternion().fromAngleNormalAxis(ax1v, ax1); Quaternion q2 = new Quaternion().fromAngleNormalAxis(ax2v, ax2); Quaternion q3 = new Quaternion().fromAngleNormalAxis(ax3v, ax3); - return q1.multLocal(q2).multLocal(q3); + return q3.multLocal(q2).multLocal(q1);// FBX uses order opposite of what stated in RotationOrder's names... Because. + } + + private static Matrix4f rotationX(float r, Matrix4f out) { + if(out == null) + out = new Matrix4f(); + else + out.loadIdentity(); + out.m11 = out.m22 = FastMath.cos(r); + out.m12 = -(out.m21 = FastMath.sin(r)); + return out; + } + + private static Matrix4f rotationY(float r, Matrix4f out) { + if(out == null) + out = new Matrix4f(); + else + out.loadIdentity(); + out.m00 = out.m22 = FastMath.cos(r); + out.m20 = -(out.m02 = FastMath.sin(r)); + return out; + } + + private static Matrix4f rotationZ(float r, Matrix4f out) { + if(out == null) + out = new Matrix4f(); + else + out.loadIdentity(); + out.m00 = out.m11 = FastMath.cos(r); + out.m01 = -(out.m10 = FastMath.sin(r)); + return out; } } 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 accbdbbf46..938598f959 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 @@ -22,7 +22,10 @@ import com.jme3.asset.AssetKey; import com.jme3.asset.AssetLoader; import com.jme3.asset.AssetManager; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; +import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.plugins.fbx.AnimationList.AnimInverval; @@ -31,6 +34,8 @@ import com.jme3.scene.plugins.fbx.file.FbxReader; import com.jme3.scene.plugins.fbx.objects.FbxAnimCurve; import com.jme3.scene.plugins.fbx.objects.FbxAnimNode; +import com.jme3.scene.plugins.fbx.objects.FbxAnimationLayer; +import com.jme3.scene.plugins.fbx.objects.FbxAnimationStack; import com.jme3.scene.plugins.fbx.objects.FbxBindPose; import com.jme3.scene.plugins.fbx.objects.FbxCluster; import com.jme3.scene.plugins.fbx.objects.FbxImage; @@ -46,6 +51,8 @@ *

Loads scene meshes, materials, textures, skeleton and skeletal animation. * Multiple animations can be defined with {@link AnimationList} passing into {@link SceneKey} * or loaded from different animation layer.

+ * + * @author Aleksandra Menshchikova, Eirenliel */ public class SceneLoader implements AssetLoader { @@ -63,23 +70,27 @@ public class SceneLoader implements AssetLoader { public AssetInfo currentAssetInfo; // Scene global settings - private float animFrameRate; - public float unitSize; + private float animFrameRate = 30; + // Untested + public float unitSize = 1.0f; public int xAxis = 1; public int yAxis = 1; public int zAxis = 1; // Scene objects - private Map allObjects = new HashMap<>(); // All supported FBX objects - private Map skinMap = new HashMap<>(); // Skin for bone clusters - private Map alayerMap = new HashMap<>(); // Amination layers - public Map modelMap = new HashMap<>(); // Nodes - private Map limbMap = new HashMap<>(); // Nodes that are actually bones - private Map bindMap = new HashMap<>(); // Node bind poses - private Map geomMap = new HashMap<>(); // Mesh geometries + private Map allObjects = new HashMap(); // All supported FBX objects + private Map skinMap = new HashMap(); // Skin for bone clusters + private Map alayerMap = new HashMap(); // Amination layers + private Map astackMap = new HashMap(); // Amination stacks + public Map modelMap = new HashMap(); // Nodes + private Map limbMap = new HashMap(); // Nodes that are actually bones + private Map bindMap = new HashMap(); // Node bind poses + private Map geomMap = new HashMap(); // Mesh geometries private Skeleton skeleton; private AnimControl animControl; public Node sceneNode; + public FbxFile sceneFile; + public List rootNodes = new ArrayList<>(); public void warning(String warning) { warnings.add(warning); @@ -103,8 +114,15 @@ public Object load(AssetInfo assetInfo) throws IOException { sceneName = sceneName.substring(sceneFolderName.length()); loadScene(stream); linkScene(); - if(warnings.size() > 0) - logger.log(Level.WARNING, "Model load finished with warnings:\n" + join(warnings, "\n")); + if(warnings.size() > 0) { + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < warnings.size(); ++i) { + if(i != 0) + sb.append("\n"); + sb.append(warnings.get(i)); + } + logger.log(Level.WARNING, "Model load finished with warnings:\n" + sb.toString()); + } } finally { releaseObjects(); if(stream != null) @@ -116,8 +134,8 @@ public Object load(AssetInfo assetInfo) throws IOException { 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) { + sceneFile = FbxReader.readFBX(stream); + for(FbxElement e : sceneFile.rootElements) { // Is it possible for elements to be in wrong order? switch(e.id) { case "GlobalSettings": @@ -160,7 +178,7 @@ private void loadGlobalSettings(FbxElement element) { } } - private void loadObjects(FbxElement element) throws IOException { + private void loadObjects(FbxElement element) { FbxObject obj = null; for(FbxElement e : element.children) { switch(e.id) { @@ -195,10 +213,15 @@ private void loadObjects(FbxElement element) throws IOException { obj = loadDeformer(e); break; case "AnimationLayer": - FbxObject layer = new FbxObject(this, e); + FbxAnimationLayer layer = new FbxAnimationLayer(this, e); obj = layer; alayerMap.put(layer.id, layer); break; + case "AnimationStack": + FbxAnimationStack stack = new FbxAnimationStack(this, e); + obj = stack; + astackMap.put(stack.id, stack); + break; case "AnimationCurve": obj = new FbxAnimCurve(this, e); break; @@ -265,9 +288,16 @@ private void linkScene() { long startTime = System.currentTimeMillis(); applySkinning(); buildAnimations(); + if(skeleton != null) { + // Bind pose may have been changed when loading animations (from frame 0) + // Reapply bone transforms as bind pose + skeleton.updateWorldVectors(); + skeleton.setBindingPose(); + skeleton.resetAndUpdate(); + } for(FbxMesh mesh : geomMap.values()) mesh.clearMaterials(); - // Remove bones from node structures : JME creates attach node by itself + // Remove bones from node structures : JME creates attach nodes by itself for(FbxNode limb : limbMap.values()) limb.node.removeFromParent(); long estimatedTime = System.currentTimeMillis() - startTime; @@ -297,125 +327,165 @@ private void applySkinning() { sceneNode.addControl(animControl); SkeletonControl control = new SkeletonControl(skeleton); sceneNode.addControl(control); + if(unitSize != 1.0) { + sceneNode.scale(unitSize); // Should it be `1f / unitSize`? Who knows... need testing + } } private void buildAnimations() { - if(skeleton == null) + if(skeleton == null || astackMap.size() == 0) return; if(animList == null || animList.list.size() == 0) { animList = new AnimationList(); - for(long layerId : alayerMap.keySet()) { - FbxObject layer = alayerMap.get(layerId); - animList.add(layer.name, layer.name, 0, -1); + for(long layerId : astackMap.keySet()) { + FbxAnimationStack stack = astackMap.get(layerId); + animList.add(stack.name, stack.name, 0, -1); } } // Extract aminations HashMap anims = new HashMap(); for(AnimInverval animInfo : animList.list) { + // Search stacks + long animationStack = 0L; + for(long stackId : astackMap.keySet()) { + FbxAnimationStack stack = astackMap.get(stackId); + if(stack.name.equals(animInfo.layerName)) { + animationStack = stackId; + break; + } + } + if(animationStack == 0 && astackMap.size() > 0) { + animationStack = astackMap.keySet().iterator().next(); + } + FbxAnimationStack stack = astackMap.get(animationStack); + if(stack == null) + continue; + // TODO Read animation length from AnimationStacks? float realLength = 0; 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()) { - FbxObject layer = alayerMap.get(layerId); - if(layer.name.equals(animInfo.layerName)) { - sourceLayerId = layerId; - break; - } - } - // Build bone tracks - for(FbxNode limb : limbMap.values()) { - // Animation channels may have different keyframes (non-baked animation). - // So we have to restore intermediate values for all channels cause of JME requires - // a bone track as a single channel with collective transformation for each keyframe - Set stamps = new TreeSet(); // Sorted unique timestamps - FbxAnimNode animTranslation = limb.animTranslation(sourceLayerId); - FbxAnimNode animRotation = limb.animRotation(sourceLayerId); - FbxAnimNode animScale = limb.animScale(sourceLayerId); - boolean haveTranslation = haveAnyChannel(animTranslation); - boolean haveRotation = haveAnyChannel(animRotation); - boolean haveScale = haveAnyChannel(animScale); - // Collect keyframes stamps - if(haveTranslation) - animTranslation.exportTimes(stamps); - if(haveRotation) - animRotation.exportTimes(stamps); - if(haveScale) - animScale.exportTimes(stamps); - if(stamps.isEmpty()) - continue; - long[] keyTimes = new long[stamps.size()]; - int cnt = 0; - for(long t : stamps) - keyTimes[cnt++] = t; - // Calculate keys interval by animation time interval - int firstKeyIndex = 0; - int lastKeyIndex = keyTimes.length - 1; - for(int i = 0; i < keyTimes.length; ++i) { - float time = (float) (((double) keyTimes[i]) * secondsPerUnit); // Translate into seconds - if(time <= animStart) - firstKeyIndex = i; - if(time >= animStop && animStop >= 0) { - lastKeyIndex = i; - break; + for(int ll = 0; ll < stack.animationLayers.size(); ++ll) { + long sourceLayerId = stack.animationLayers.get(ll); + // Build bone tracks + for(FbxNode limb : limbMap.values()) { + // Animation channels may have different keyframes (non-baked animation). + // So we have to restore intermediate values for all channels cause of JME requires + // a bone track as a single channel with collective transformation for each keyframe + Set stamps = new TreeSet(); // Sorted unique timestamps + FbxAnimNode animTranslation = limb.animTranslation(sourceLayerId); + FbxAnimNode animRotation = limb.animRotation(sourceLayerId); + FbxAnimNode animScale = limb.animScale(sourceLayerId); + boolean haveTranslation = haveAnyChannel(animTranslation); + boolean haveRotation = haveAnyChannel(animRotation); + boolean haveScale = haveAnyChannel(animScale); + // Collect keyframes stamps + if(haveTranslation) + animTranslation.exportTimes(stamps); + if(haveRotation) + animRotation.exportTimes(stamps); + if(haveScale) + animScale.exportTimes(stamps); + if(stamps.isEmpty()) + continue; + long[] keyTimes = new long[stamps.size()]; + int cnt = 0; + for(long t : stamps) + keyTimes[cnt++] = t; + // Calculate keys interval by animation time interval + int firstKeyIndex = 0; + int lastKeyIndex = keyTimes.length - 1; + for(int i = 0; i < keyTimes.length; ++i) { + float time = (float) (((double) keyTimes[i]) * secondsPerUnit); // Translate into seconds + if(time <= animStart) + firstKeyIndex = i; + if(time >= animStop && animStop >= 0) { + lastKeyIndex = i; + break; + } } - } - int keysCount = lastKeyIndex - firstKeyIndex + 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 = firstKeyIndex + i; - float time = (float) (((double) keyTimes[keyIndex]) * secondsPerUnit); // Translate into seconds - times[i] = time - animStart; - realLength = Math.max(realLength, times[i]); - } - // Load keyframes from animation curves - if(haveTranslation) { + int keysCount = lastKeyIndex - firstKeyIndex + 1; + if(keysCount <= 0) + continue; + float[] times = new float[keysCount]; + Vector3f[] translations = new Vector3f[keysCount]; + Quaternion[] rotations = new Quaternion[keysCount]; + Vector3f[] rotationsRaw = new Vector3f[keysCount]; + Vector3f[] scales = new Vector3f[keysCount]; + // Calculate keyframes times for(int i = 0; i < keysCount; ++i) { int keyIndex = firstKeyIndex + i; - FbxAnimNode n = animTranslation; - Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value).subtractLocal(n.value); // Why do it here but not in other places? FBX magic? - translations[i] = tvec.divideLocal(unitSize); + float time = (float) (((double) keyTimes[keyIndex]) * secondsPerUnit); // Translate into seconds + times[i] = time - animStart; + realLength = Math.max(realLength, times[i]); } - } else { - for(int i = 0; i < keysCount; ++i) - translations[i] = Vector3f.ZERO; - } - RotationOrder ro = RotationOrder.EULER_XYZ; - if(haveRotation) { - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKeyIndex + i; - FbxAnimNode n = animRotation; - Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value); - rotations[i] = ro.rotate(tvec); + Vector3f translationBase = animTranslation.value; + if(animTranslation.hasValue(0)) + translationBase = animTranslation.getValue(0, translationBase); + Vector3f rotationBase = animRotation.value; + if(animRotation.hasValue(0)) + rotationBase = animRotation.getValue(0, rotationBase); + Vector3f scaleBase = haveScale ? animScale.value : Vector3f.UNIT_XYZ; + if(haveScale && animScale.hasValue(0)) + scaleBase = animScale.getValue(0, scaleBase); + + // Sometimes bind pose is saved in 0 frame (why? because FBX & Maya...) + if(animTranslation.hasValue(0) && animRotation.hasValue(0)) { + limb.transformMatrix = limb.computeTransformationMatrix(translationBase, rotationBase, scaleBase, limb.rotationOrder); + limb.localTransform = new Transform(limb.transformMatrix.toTranslationVector(), limb.transformMatrix.toRotationQuat(), limb.transformMatrix.toScaleVector()); + limb.node.setLocalTransform(limb.localTransform); + limb.bone.setBindTransforms(limb.node.getLocalTranslation(), limb.node.getLocalRotation(), limb.node.getLocalScale()); + // TODO This ruins InheritType.Rrs } - } else { - for(int i = 0; i < keysCount; ++i) - rotations[i] = Quaternion.IDENTITY; - } - if(haveScale) { - scales = new Vector3f[keysCount]; + + // Load keyframes from animation curves + if(haveTranslation) { + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animTranslation; + Vector3f tvec = n.getValue(keyTimes[keyIndex], translationBase); + translations[i] = tvec.multLocal(unitSize); + } + } + if(haveRotation) { + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animRotation; + Vector3f tvec = n.getValue(keyTimes[keyIndex], rotationBase); + rotationsRaw[i] = tvec; + } + } + if(haveScale) { + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animScale; + Vector3f tvec = n.getValue(keyTimes[keyIndex], scaleBase); + if(limb.inheritType == InheritType.Rrs) { + tvec.multLocal(limb.node.getLocalScale()); + } + scales[i] = tvec; + } + } + haveScale = false; + // We need to transform animation from parent-space transforms to bone-space transforms + // JME uses bone-space transforms and FBx uses parent-space transforms + Transform transformBase = limb.localTransform; for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKeyIndex + i; - FbxAnimNode n = animScale; - Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value); - scales[i] = tvec; + Matrix4f transformation = limb.computeTransformationMatrix(haveTranslation ? translations[i] : translationBase, haveRotation ? rotationsRaw[i] : rotationBase, haveScale ? scales[i] : scaleBase, limb.rotationOrder); + translations[i] = transformation.toTranslationVector().subtractLocal(transformBase.getTranslation()); + rotations[i] = transformBase.getRotation().inverse().multLocal(transformation.toRotationQuat()); + scales[i] = transformation.toScaleVector().divideLocal(transformBase.getScale()); + if(haveScale || scales[i].subtract(Vector3f.UNIT_XYZ).lengthSquared() > FastMath.FLT_EPSILON) + haveScale = true; } + BoneTrack track = null; + if(haveScale) + track = new BoneTrack(limb.boneIndex, times, translations, rotations, scales); + else // No scale or scale was compensated + track = new BoneTrack(limb.boneIndex, times, translations, rotations); + anim.addTrack(track); } - 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); } if(realLength != length && animInfo.lastFrame == -1) { Track[] tracks = anim.getTracks(); @@ -430,8 +500,13 @@ private void buildAnimations() { animControl.setAnimations(anims); } + private static boolean haveAnyChannel(FbxAnimNode anims) { + return anims != null && anims.haveAnyChannel(); + } + private void releaseObjects() { // Reset settings + sceneFile = null; unitSize = 1; animFrameRate = 30; xAxis = 1; @@ -453,23 +528,10 @@ private void releaseObjects() { limbMap.clear(); bindMap.clear(); geomMap.clear(); + astackMap.clear(); + rootNodes.clear(); skeleton = null; animControl = null; sceneNode = null; } - - - private static boolean haveAnyChannel(FbxAnimNode anims) { - return anims != null && anims.haveAnyChannel(); - } - - private static String join(List list, String glue) { - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < list.size(); ++i) { - if(sb.length() != 0) - sb.append(glue); - sb.append(list.get(i)); - } - return sb.toString(); - } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java index 3952161569..8961a98d47 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java @@ -26,24 +26,38 @@ public FbxAnimCurve(SceneLoader scene, FbxElement element) { } } - public float getValue(long time) { + public boolean hasValue(long time) { + for(int i = 0; i < keyTimes.length; ++i) { + if(keyTimes[i] == time) { // hit the keyframe + return true; + } else if(keyTimes[i] > time) { + break; + } + } + return false; + } + + public float getValue(long time, float def) { // Search animation interval for(int i = 0; i < keyTimes.length; ++i) { if(keyTimes[i] == time) { // hit the keyframe return keyValues[i]; } else if(keyTimes[i] > time) { if(i == 0) { // left from the whole range - return defaultValue;//keyValues[0]; + return keyValues[0]; } else { // Interpolate between two keyframes float dt = (float) (keyTimes[i] - keyTimes[i - 1]); float dtInt = (float) (time - keyTimes[i - 1]); float dv = keyValues[i] - keyValues[i - 1]; - return keyValues[i - 1] + dv * (dtInt / dt); + float result = keyValues[i - 1] + dv * (dtInt / dt); + // For proper interpolation we need to use Bezier curves from KeyAttrDataFloat + // Just bake your animations... + return result; } } } // right from the whole range - return defaultValue;//keyValues[keyValues.length - 1]; + return keyValues[keyValues.length - 1]; } } \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java index 7febc92d16..be729ad5fe 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java @@ -68,19 +68,26 @@ public boolean haveAnyChannel() { public void exportTimes(Collection stamps) { if(xCurve != null) for(long t : xCurve.keyTimes) - stamps.add(t); + if(t != 0) + stamps.add(t); if(yCurve != null) for(long t : yCurve.keyTimes) - stamps.add(t); + if(t != 0) + stamps.add(t); if(zCurve != null) for(long t : zCurve.keyTimes) - stamps.add(t); + if(t != 0) + stamps.add(t); + } + + public boolean hasValue(long time) { + return xCurve.hasValue(time) || yCurve.hasValue(time) || zCurve.hasValue(time); } public Vector3f getValue(long time, Vector3f defaultValue) { - float xValue = (xCurve != null) ? xCurve.getValue(time) : defaultValue.x; - float yValue = (yCurve != null) ? yCurve.getValue(time) : defaultValue.y; - float zValue = (zCurve != null) ? zCurve.getValue(time) : defaultValue.z; + float xValue = (xCurve != null) ? xCurve.getValue(time, defaultValue.x) : defaultValue.x; + float yValue = (yCurve != null) ? yCurve.getValue(time, defaultValue.y) : defaultValue.y; + float zValue = (zCurve != null) ? zCurve.getValue(time, defaultValue.z) : defaultValue.z; return new Vector3f(xValue, yValue, zValue); } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimationLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimationLayer.java new file mode 100644 index 0000000000..1d55cb0dca --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimationLayer.java @@ -0,0 +1,18 @@ +package com.jme3.scene.plugins.fbx.objects; + +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxAnimationLayer extends FbxObject { + + public FbxAnimationLayer(SceneLoader scene, FbxElement element) { + super(scene, element); + } + + @Override + public void link(FbxObject child) { + if(child instanceof FbxAnimNode) { + ((FbxAnimNode) child).layerId = id; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimationStack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimationStack.java new file mode 100644 index 0000000000..43977e7f60 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimationStack.java @@ -0,0 +1,25 @@ +package com.jme3.scene.plugins.fbx.objects; + +import java.util.ArrayList; +import java.util.List; + +import com.jme3.scene.plugins.fbx.SceneLoader; +import com.jme3.scene.plugins.fbx.file.FbxElement; + +public class FbxAnimationStack extends FbxObject { + + public List animationLayers = new ArrayList(); + public long start; + public long stop; + + public FbxAnimationStack(SceneLoader scene, FbxElement element) { + super(scene, element); + } + + @Override + public void link(FbxObject child) { + if(child instanceof FbxAnimationLayer) { + animationLayers.add(child.id); + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java index 1170f9718c..0bdb5ac497 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java @@ -10,7 +10,7 @@ public class FbxBindPose extends FbxObject { - public Map nodeTransforms = new HashMap<>(); + public Map nodeTransforms = new HashMap(); public FbxBindPose(SceneLoader scene, FbxElement element) { super(scene, element); @@ -38,7 +38,7 @@ public FbxBindPose(SceneLoader scene, FbxElement element) { } public void fillBindTransforms() { - for(long nodeId : nodeTransforms.keySet()) { + for(Long nodeId : nodeTransforms.keySet()) { FbxNode node = scene.modelMap.get(nodeId); node.bindTransform = nodeTransforms.get(nodeId).clone(); } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java index c16d253add..6dbfbd5e2f 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java @@ -96,7 +96,7 @@ public void link(FbxObject otherObject, String propertyName) { private Material createMaterial() { Material m = new Material(scene.assetManager, "Common/MatDefs/Light/Lighting.j3md"); - m.setName(name); + m.setName(name.replace(':', '_')); ambientColor.multLocal(ambientFactor); diffuseColor.multLocal(diffuseFactor); specularColor.multLocal(specularFactor); @@ -105,7 +105,7 @@ private Material createMaterial() { m.setColor("Specular", new ColorRGBA(specularColor.x, specularColor.y, specularColor.z, 1)); m.setFloat("Shininess", shininessExponent); m.setBoolean("UseMaterialColors", true); - m.setFloat("AlphaDiscardThreshold", 0.5f); // TODO replace with right way in JME to set "Aplha Test" + m.setFloat("AlphaDiscardThreshold", 0.1f); // TODO replace with right way in JME to set "Aplha Test" m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); return m; } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java index 930dd830d3..0893cbcab7 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java @@ -1,48 +1,50 @@ package com.jme3.scene.plugins.fbx.objects; -import java.io.IOException; import java.nio.FloatBuffer; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import com.jme3.asset.AssetLoadException; +import com.jme3.material.Material; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; +import com.jme3.scene.plugins.fbx.FBXLoadingException; import com.jme3.scene.plugins.fbx.SceneLoader; import com.jme3.scene.plugins.fbx.file.FbxElement; import com.jme3.scene.Mesh.Mode; import com.jme3.scene.Node; import com.jme3.util.BufferUtils; -import com.jme3.util.IntMap; -import com.jme3.util.IntMap.Entry; public class FbxMesh extends FbxObject { public double[] vertices; public int[] indices; public int[] edges; - public String normalsMapping; - public String normalsReference; + public String normalsMapping = ""; + public String normalsReference = ""; public double[] normals; - public String tangentsMapping; - public String tangentsReference; + public String tangentsMapping = ""; + public String tangentsReference = ""; public double[] tangents; - public String binormalsMapping; - public String binormalsReference; + public String binormalsMapping = ""; + public String binormalsReference = ""; public double[] binormals; - public String uvMapping; - public String uvReference; + public String uvMapping = ""; + public String uvReference = ""; public double[] uv; public int[] uvIndex; - public List uvIndexes = new ArrayList<>(); - public List uvs = new ArrayList<>(); - public String smoothingMapping; - public String smoothingReference; + public List uvIndexes = new ArrayList(); + public List uvs = new ArrayList(); + public String smoothingMapping = ""; + public String smoothingReference = ""; public int[] smoothing; - public String materialsMapping; - public String materialsReference; + public String materialsMapping = ""; + public String materialsReference = ""; public int[] materials; // Build helping data public int iCount; @@ -56,7 +58,7 @@ public class FbxMesh extends FbxObject { public FbxNode parent; public int lastMaterialId = 0; - public FbxMesh(SceneLoader scene, FbxElement element) throws IOException { + public FbxMesh(SceneLoader scene, FbxElement element) { super(scene, element); if(type.equals("Mesh")) { data: for(FbxElement e : element.children) { @@ -228,6 +230,8 @@ public FbxMesh(SceneLoader scene, FbxElement element) throws IOException { } public void setParent(Node node) { + if(geometries == null) + return; for(int i = 0; i < geometries.size(); ++i) { Geometry geom = geometries.get(i); geom.setName(node.getName() + (i > 0 ? "-" + i : "")); @@ -242,6 +246,8 @@ public void linkToZero() { } public void clearMaterials() { + if(geometries == null) + return; for(Geometry g : geometries) { if(g.getUserData("FBXMaterial") != null) g.setUserData("FBXMaterial", null); @@ -256,7 +262,7 @@ public void link(FbxObject otherObject) { } } - private List createGeometries() throws IOException { + private List createGeometries() { Mesh mesh = new Mesh(); mesh.setMode(Mode.Triangles); // Since each vertex should contain unique texcoord and normal we should unroll vertex indexing @@ -285,8 +291,8 @@ private List createGeometries() throws IOException { } } // Unroll index array into vertex mapping - vertexMap = new ArrayList<>(vCount); - indexMap = new ArrayList<>(vCount); + vertexMap = new ArrayList(vCount); + indexMap = new ArrayList(vCount); polyVertCount = 0; for(int i = 0; i < iCount; ++i) { int index = indices[i]; @@ -318,7 +324,7 @@ private List createGeometries() throws IOException { } } // Build reverse vertex mapping - reverseVertexMap = new ArrayList<>(srcVertexCount); + reverseVertexMap = new ArrayList>(srcVertexCount); for(int i = 0; i < srcVertexCount; ++i) reverseVertexMap.add(new ArrayList()); for(int i = 0; i < vCount; ++i) { @@ -328,15 +334,15 @@ private List createGeometries() throws IOException { } else { // Stub for no vertex indexing (direct mapping) iCount = vCount = srcVertexCount; - vertexMap = new ArrayList<>(vCount); - indexMap = new ArrayList<>(vCount); - reverseVertexMap = new ArrayList<>(vCount); + vertexMap = new ArrayList(vCount); + indexMap = new ArrayList(vCount); + reverseVertexMap = new ArrayList>(vCount); for(int i = 0; i < vCount; ++i) { vertexMap.set(i, i); indexMap.set(i, i); - List l = new ArrayList(1); - l.add(i); - reverseVertexMap.add(l); + ArrayList list = new ArrayList(); + list.add(i); + reverseVertexMap.add(list); } } if(vertices != null) { @@ -364,7 +370,7 @@ private List createGeometries() throws IOException { else if(normalsMapping.equals("ByPolygonVertex")) mapping = indexMap; else - throw new IOException("Unknown normals mapping type: " + normalsMapping); + throw new FBXLoadingException("Unknown normals mapping type: " + normalsMapping); int srcCount = normals.length / 3; for(int i = 0; i < vCount; ++i) { int index = mapping.get(i); @@ -386,7 +392,7 @@ else if(normalsMapping.equals("ByPolygonVertex")) else if(tangentsMapping.equals("ByPolygonVertex")) mapping = indexMap; else - throw new IOException("Unknown tangents mapping type: " + tangentsMapping); + throw new FBXLoadingException("Unknown tangents mapping type: " + tangentsMapping); int srcCount = tangents.length / 3; for(int i = 0; i < vCount; ++i) { int index = mapping.get(i); @@ -408,7 +414,7 @@ else if(tangentsMapping.equals("ByPolygonVertex")) else if(binormalsMapping.equals("ByPolygonVertex")) mapping = indexMap; else - throw new IOException("Unknown binormals mapping type: " + binormalsMapping); + throw new FBXLoadingException("Unknown binormals mapping type: " + binormalsMapping); int srcCount = binormals.length / 3; for(int i = 0; i < vCount; ++i) { int index = mapping.get(i); @@ -429,7 +435,7 @@ else if(binormalsMapping.equals("ByPolygonVertex")) if(uvIndexSrcCount != iCount) throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + iCount); // Unroll UV index array - unIndexMap = new ArrayList<>(vCount); + unIndexMap = new ArrayList(vCount); int polyVertCount = 0; for(int i = 0; i < iCount; ++i) { int index = indices[i]; @@ -490,29 +496,33 @@ else if(binormalsMapping.equals("ByPolygonVertex")) } List geometries = new ArrayList(); if(materialsReference.equals("IndexToDirect") && materialsMapping.equals("ByPolygon")) { - IntMap> indexBuffers = new IntMap<>(); + Map> indexBuffers = new HashMap>(); for(int polygon = 0; polygon < materials.length; ++polygon) { int material = materials[polygon]; List list = indexBuffers.get(material); if(list == null) { - list = new ArrayList<>(); + list = new ArrayList(); indexBuffers.put(material, list); } list.add(polygon * 3 + 0); list.add(polygon * 3 + 1); list.add(polygon * 3 + 2); } - Iterator>> iterator = indexBuffers.iterator(); + Iterator>> iterator = indexBuffers.entrySet().iterator(); while(iterator.hasNext()) { - Entry> e = iterator.next(); + Entry> e = iterator.next(); int materialId = e.getKey(); List indexes = e.getValue(); Mesh newMesh = mesh.clone(); - newMesh.setBuffer(VertexBuffer.Type.Index, 3, toArray(indexes.toArray(new Integer[indexes.size()]))); + int[] array = new int[indexes.size()]; + for(int i = 0; i < indexes.size(); ++i) + array[i] = indexes.get(i); + newMesh.setBuffer(VertexBuffer.Type.Index, 3, array); newMesh.setStatic(); newMesh.updateBound(); newMesh.updateCounts(); Geometry geom = new Geometry(); + setupEmptyMaterial(geom); geom.setMesh(newMesh); geometries.add(geom); geom.setUserData("FBXMaterial", materialId); @@ -522,16 +532,14 @@ else if(binormalsMapping.equals("ByPolygonVertex")) mesh.updateBound(); mesh.updateCounts(); Geometry geom = new Geometry(); + setupEmptyMaterial(geom); geom.setMesh(mesh); geometries.add(geom); } return geometries; } - private static int[] toArray(Integer[] arr) { - int[] ret = new int[arr.length]; - for(int i = 0; i < arr.length; ++i) - ret[i] = arr[i].intValue(); - return ret; + protected void setupEmptyMaterial(Geometry g) { + g.setMaterial(new Material(scene.assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java index ebb2094928..d7a6012008 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java @@ -1,24 +1,29 @@ package com.jme3.scene.plugins.fbx.objects; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; import com.jme3.material.Material; import com.jme3.material.RenderState.FaceCullMode; import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.Node; +import com.jme3.scene.plugins.fbx.InheritType; import com.jme3.scene.plugins.fbx.RotationOrder; import com.jme3.scene.plugins.fbx.SceneLoader; import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxNode extends FbxObject { + public Map userData = new HashMap(); public FaceCullMode cullMode = FaceCullMode.Back; public Transform localTransform; public Node node; @@ -26,33 +31,42 @@ public class FbxNode extends FbxObject { public boolean rotationActive = false; public RotationOrder rotationOrder = RotationOrder.EULER_XYZ; + public InheritType inheritType = InheritType.RrSs; // For bones and animation, in world space public Matrix4f bindTransform = null; public int boneIndex; - public Map animTranslations = new HashMap<>(); - public Map animRotations = new HashMap<>(); - public Map animScales = new HashMap<>(); + public Map animTranslations = new HashMap(); + public Map animRotations = new HashMap(); + public Map animScales = new HashMap(); public Bone bone; private FbxAnimNode lastAnimTranslation; private FbxAnimNode lastAnimRotation; private FbxAnimNode lastAnimScale; private FbxMesh mesh; - public Map skinToCluster = new HashMap<>(); + public Map skinToCluster = new HashMap(); + public List children = new ArrayList(); + + /** + * Cache to store materials if linking order is wrong + */ + private List wrongOrderMaterial = new ArrayList(); + + public Vector3f translationLocalRaw = new Vector3f(); + public Vector3f rotationOffsetRaw = new Vector3f(); + public Vector3f rotationPivotRaw = new Vector3f(); + public Vector3f rotationPreRaw = new Vector3f(); + public Vector3f rotationLocalRaw = new Vector3f(); + public Vector3f rotationPostRaw = new Vector3f(); + public Vector3f scaleOffsetRaw = new Vector3f(); + public Vector3f scalePivotRaw = new Vector3f(); + public Vector3f scaleLocalRaw = new Vector3f(1, 1, 1); + + public Matrix4f transformMatrix; public FbxNode(SceneLoader scene, FbxElement element) { super(scene, element); - node = new Node(name); - Vector3f translationLocalRaw = new Vector3f(); - Vector3f rotationOffsetRaw = new Vector3f(); - Vector3f rotationPivotRaw = new Vector3f(); - Vector3f rotationPreRaw = new Vector3f(); - Vector3f rotationLocalRaw = new Vector3f(); - Vector3f rotationPostRaw = new Vector3f(); - Vector3f scaleOffsetRaw = new Vector3f(); - Vector3f scalePivotRaw = new Vector3f(); - Vector3f scaleLocalRaw = new Vector3f(1, 1, 1); for(FbxElement prop : element.getFbxProperties()) { double x, y, z; String propName = (String) prop.properties.get(0); @@ -87,6 +101,9 @@ public FbxNode(SceneLoader scene, FbxElement element) { case "ScalePivot": readVectorFromProp(scalePivotRaw, prop); break; + case "InheritType": + inheritType = InheritType.values[(Integer) prop.properties.get(4)]; + break; case "U": String userDataKey = (String) prop.properties.get(0); String userDataType = (String) prop.properties.get(1); @@ -108,7 +125,7 @@ public FbxNode(SceneLoader scene, FbxElement element) { scene.warning("Unsupported user data type: " + userDataType + ". Ignoring."); continue; } - node.setUserData(userDataKey, userDataValue); + userData.put(userDataKey, userDataValue); break; } } @@ -136,40 +153,54 @@ public FbxNode(SceneLoader scene, FbxElement element) { ScalingPivotInverse: inverse of ScalingPivot */ - RotationOrder rotOrder = rotationActive ? rotationOrder : RotationOrder.EULER_XYZ; + transformMatrix = computeTransformationMatrix(translationLocalRaw, rotationLocalRaw, scaleLocalRaw, rotationOrder); + localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector()); + + node = new Node(name); + if(userData.size() > 0) { + Iterator> iterator = userData.entrySet().iterator(); + while(iterator.hasNext()) { + Entry e = iterator.next(); + node.setUserData(e.getKey(), e.getValue()); + } + } + node.setLocalTransform(localTransform); + } + + public Matrix4f computeTransformationMatrix(Vector3f rawTranslation, Vector3f rawRotation, Vector3f rawScale, RotationOrder rotOrder) { Matrix4f transformMatrix = new Matrix4f(); - transformMatrix.setTranslation(translationLocalRaw.x + rotationOffsetRaw.x + rotationPivotRaw.x, translationLocalRaw.y + rotationOffsetRaw.y + rotationPivotRaw.y, translationLocalRaw.z + rotationOffsetRaw.z + rotationPivotRaw.z); + + Matrix4f mat = new Matrix4f(); + mat.setTranslation(rawTranslation.x + rotationOffsetRaw.x + rotationPivotRaw.x, rawTranslation.y + rotationOffsetRaw.y + rotationPivotRaw.y, rawTranslation.z + rotationOffsetRaw.z + rotationPivotRaw.z); + transformMatrix.multLocal(mat); if(rotationActive) { - Quaternion postRotation = rotOrder.rotate(rotationPostRaw.x, rotationPostRaw.y, rotationPostRaw.z); - Quaternion localRotation = rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z); - Quaternion preRotation = rotOrder.rotate(rotationPreRaw.x, rotationPreRaw.y, rotationPreRaw.z); - //preRotation.multLocal(localRotation).multLocal(postRotation); - postRotation.multLocal(localRotation).multLocal(preRotation); + // Because of majic, FBX uses rotation order only to Lcl Rotations. Pre Rotations (Joint Orient) uses always XYZ order + // What is Post Rotations is still a mystery + Matrix4f preRotation = RotationOrder.EULER_XYZ.rotateToMatrix(rotationPreRaw.x, rotationPreRaw.y, rotationPreRaw.z); + Matrix4f localRotation = rotOrder.rotateToMatrix(rawRotation.x, rawRotation.y, rawRotation.z); + Matrix4f postRotation = RotationOrder.EULER_XYZ.rotateToMatrix(rotationPostRaw.x, rotationPostRaw.y, rotationPostRaw.z); + transformMatrix.multLocal(preRotation); + transformMatrix.multLocal(localRotation); transformMatrix.multLocal(postRotation); } else { - transformMatrix.multLocal(rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z)); + transformMatrix.multLocal(RotationOrder.EULER_XYZ.rotate(rawRotation.x, rawRotation.y, rawRotation.z)); } - - Matrix4f mat = new Matrix4f(); mat.setTranslation(scaleOffsetRaw.x + scalePivotRaw.x - rotationPivotRaw.x, scaleOffsetRaw.y + scalePivotRaw.y - rotationPivotRaw.y, scaleOffsetRaw.z + scalePivotRaw.z - rotationPivotRaw.z); transformMatrix.multLocal(mat); - - transformMatrix.scale(scaleLocalRaw); - transformMatrix.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize)); - + transformMatrix.scale(rawScale); mat.setTranslation(scalePivotRaw.negate()); transformMatrix.multLocal(mat); - localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector()); + return transformMatrix; - node.setLocalTransform(localTransform); } @Override public void linkToZero() { scene.sceneNode.attachChild(node); + scene.rootNodes.add(this); } public void setSkeleton(Skeleton skeleton) { @@ -236,6 +267,10 @@ public void link(FbxObject otherObject) { if(otherObject instanceof FbxMaterial) { FbxMaterial m = (FbxMaterial) otherObject; Material mat = m.material; + if(mesh == null) { + wrongOrderMaterial.add(mat); + return; + } if(cullMode != FaceCullMode.Back) mat.getAdditionalRenderState().setFaceCullMode(cullMode); for(Geometry g : mesh.geometries) { @@ -250,6 +285,11 @@ public void link(FbxObject otherObject) { } else if(otherObject instanceof FbxNode) { FbxNode n = (FbxNode) otherObject; node.attachChild(n.node); + children.add(n); + if(n.inheritType == InheritType.Rrs) { + Vector3f scale = node.getWorldScale(); + n.node.scale(1f / scale.x, 1f / scale.y, 1f / scale.z); + } n.parentFbxNode = this; if(isLimb() && n.isLimb()) { if(bone == null) @@ -263,6 +303,20 @@ public void link(FbxObject otherObject) { m.setParent(node); m.parent = this; mesh = m; + if(wrongOrderMaterial.size() > 0) { + for(int i = 0; i < wrongOrderMaterial.size(); ++i) { + Material mat = wrongOrderMaterial.remove(i--); + for(Geometry g : mesh.geometries) { + if(g.getUserData("FBXMaterial") != null) { + if((Integer) g.getUserData("FBXMaterial") == mesh.lastMaterialId) + g.setMaterial(mat); + } else { + g.setMaterial(mat); + } + } + mesh.lastMaterialId++; + } + } } } From be89439087b0c349b5244376475b2bc27bb89a5e Mon Sep 17 00:00:00 2001 From: Eirenliel Allier Date: Tue, 3 Jan 2017 00:33:59 +0300 Subject: [PATCH 3/3] Fixed loading animation scales from FBX --- .../src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java | 1 - 1 file changed, 1 deletion(-) 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 938598f959..14ebca89f8 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 @@ -467,7 +467,6 @@ private void buildAnimations() { scales[i] = tvec; } } - haveScale = false; // We need to transform animation from parent-space transforms to bone-space transforms // JME uses bone-space transforms and FBx uses parent-space transforms Transform transformBase = limb.localTransform;