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..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
@@ -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,164 @@ 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;
+ }
+ }
+ // 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 +499,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 +527,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/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);
}
}
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++;
+ }
+ }
}
}