Pārlūkot izejas kodu

jme3-plugins: convert tabs to spaces

Stephen Gold 3 gadi atpakaļ
vecāks
revīzija
3d2d6b4e18
25 mainītis faili ar 2258 papildinājumiem un 2258 dzēšanām
  1. 36 36
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java
  2. 36 36
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java
  3. 41 41
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java
  4. 44 44
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java
  5. 17 17
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java
  6. 424 424
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
  7. 49 49
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneWithAnimationLoader.java
  8. 26 26
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java
  9. 8 8
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java
  10. 183 183
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java
  11. 42 42
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java
  12. 76 76
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java
  13. 43 43
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java
  14. 35 35
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java
  15. 107 107
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java
  16. 102 102
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java
  17. 515 515
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java
  18. 251 251
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java
  19. 32 32
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java
  20. 132 132
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java
  21. 31 31
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java
  22. 5 5
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
  23. 7 7
      jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java
  24. 13 13
      jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMeshLoader.java
  25. 3 3
      jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java

+ 36 - 36
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java

@@ -42,41 +42,41 @@ import java.util.List;
  * Skeletal animations will be created if only scene contain skeletal bones</p>
  */
 public class AnimationList {
-	
-	List<AnimInverval> list = new ArrayList<>();
-	
-	/**
-	 * Use in the case of multiple animation layers in FBX asset
-         *
-         * @param name - animation name to access via {@link com.jme3.animation.AnimControl}
-         * @param firstFrame the index of the first frame
-         * @param lastFrame the index of the last frame
-         */
-	public void add(String name, int firstFrame, int lastFrame) {
-		add(name, null, firstFrame, lastFrame);
-	}
 
-        /**
-         * Use in the case of multiple animation layers in FBX asset
-         *
-         * @param name - animation name to access via {@link com.jme3.animation.AnimControl}
-         * @param layerName - source layer
-         * @param firstFrame the index of the first frame
-         * @param lastFrame the index of the last frame
-         */
-	public void add(String name, String layerName, int firstFrame, int lastFrame) {
-		AnimInverval cue = new AnimInverval();
-		cue.name = name;
-		cue.layerName = layerName;
-		cue.firstFrame = firstFrame;
-		cue.lastFrame = lastFrame;
-		list.add(cue);
-	}
-	
-	static class AnimInverval {
-		String name;
-		String layerName;
-		int firstFrame;
-		int lastFrame;
-	}
+    List<AnimInverval> list = new ArrayList<>();
+
+    /**
+     * Use in the case of multiple animation layers in FBX asset
+     *
+     * @param name - animation name to access via {@link com.jme3.animation.AnimControl}
+     * @param firstFrame the index of the first frame
+     * @param lastFrame the index of the last frame
+     */
+    public void add(String name, int firstFrame, int lastFrame) {
+        add(name, null, firstFrame, lastFrame);
+    }
+
+    /**
+     * Use in the case of multiple animation layers in FBX asset
+     *
+     * @param name - animation name to access via {@link com.jme3.animation.AnimControl}
+     * @param layerName - source layer
+     * @param firstFrame the index of the first frame
+     * @param lastFrame the index of the last frame
+     */
+    public void add(String name, String layerName, int firstFrame, int lastFrame) {
+        AnimInverval cue = new AnimInverval();
+        cue.name = name;
+        cue.layerName = layerName;
+        cue.firstFrame = firstFrame;
+        cue.lastFrame = lastFrame;
+        list.add(cue);
+    }
+
+    static class AnimInverval {
+        String name;
+        String layerName;
+        int firstFrame;
+        int lastFrame;
+    }
 }

+ 36 - 36
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java

@@ -45,41 +45,41 @@ import java.io.IOException;
  * <p>Filename is required to acquire proper type asset loader according to extension.</p>
  */
 public class ContentTextureKey extends TextureKey {
-	
-	private byte[] content;
-	
-	public ContentTextureKey(String name, byte[] content) {
-		super(name);
-		this.content = content;
-	}
-	
-	public byte[] getContent() {
-		return content;
-	}
-	
-        @Override
-        public Class<? extends AssetCache> getCacheType(){
-            // Need to override this so that textures embedded in FBX
-            // don't get cached by the asset manager.
-            return null;
-        }
+
+    private byte[] content;
+
+    public ContentTextureKey(String name, byte[] content) {
+        super(name);
+        this.content = content;
+    }
+
+    public byte[] getContent() {
+        return content;
+    }
+
+    @Override
+    public Class<? extends AssetCache> getCacheType(){
+        // Need to override this so that textures embedded in FBX
+        // don't get cached by the asset manager.
+        return null;
+    }
         
-	@Override
-	public String toString() {
-		return super.toString() + " " + content.length + " bytes";
-	}
-	
-	@Override
-	public void write(JmeExporter ex) throws IOException {
-		super.write(ex);
-		OutputCapsule oc = ex.getCapsule(this);
-		oc.write(content, "content", new byte[0]);
-	}
-	
-	@Override
-	public void read(JmeImporter im) throws IOException {
-		super.read(im);
-		InputCapsule ic = im.getCapsule(this);
-		content = ic.readByteArray("content", new byte[0]);
-	}
+    @Override
+    public String toString() {
+        return super.toString() + " " + content.length + " bytes";
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(content, "content", new byte[0]);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        content = ic.readByteArray("content", new byte[0]);
+    }
 }

+ 41 - 41
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java

@@ -45,45 +45,45 @@ import java.util.logging.Logger;
  * Used to locate a resource based on a {@link ContentTextureKey}.
  */
 public class ContentTextureLocator implements AssetLocator {
-	
-	private static final Logger logger = Logger.getLogger(ContentTextureLocator.class.getName());
-	
-	@Override
-	public void setRootPath(String rootPath) {
-	}
-	
-	@SuppressWarnings("rawtypes")
-	@Override
-	public AssetInfo locate(AssetManager manager, AssetKey key) {
-		if(key instanceof ContentTextureKey) {
-			String name = key.getName();
-			byte[] content = ((ContentTextureKey) key).getContent();
-			if(content != null) {
-				return new ContentAssetInfo(manager, key, content);
-			} else {
-				logger.log(Level.WARNING, "No content for " + name);
-				return null;
-			}
-		} else {
-			logger.log(Level.SEVERE, "AssetKey should be TextureContentKey instance");
-			return null;
-		}
-	}
-	
-	private class ContentAssetInfo extends AssetInfo {
-		
-		private InputStream stream;
-		
-		@SuppressWarnings("rawtypes")
-		public ContentAssetInfo(AssetManager assetManager, AssetKey key, byte[] content) {
-			super(assetManager, key);
-			this.stream = (content != null) ? new ByteArrayInputStream(content) : null;
-		}
-		
-		@Override
-		public InputStream openStream() {
-			return stream;
-		}
-	}
-	
+
+    private static final Logger logger = Logger.getLogger(ContentTextureLocator.class.getName());
+
+    @Override
+    public void setRootPath(String rootPath) {
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public AssetInfo locate(AssetManager manager, AssetKey key) {
+        if(key instanceof ContentTextureKey) {
+            String name = key.getName();
+            byte[] content = ((ContentTextureKey) key).getContent();
+            if(content != null) {
+                return new ContentAssetInfo(manager, key, content);
+            } else {
+                logger.log(Level.WARNING, "No content for " + name);
+                return null;
+            }
+        } else {
+            logger.log(Level.SEVERE, "AssetKey should be TextureContentKey instance");
+            return null;
+        }
+    }
+
+    private class ContentAssetInfo extends AssetInfo {
+
+        private InputStream stream;
+
+        @SuppressWarnings("rawtypes")
+        public ContentAssetInfo(AssetManager assetManager, AssetKey key, byte[] content) {
+            super(assetManager, key);
+            this.stream = (content != null) ? new ByteArrayInputStream(content) : null;
+        }
+
+        @Override
+        public InputStream openStream() {
+            return stream;
+        }
+    }
+
 }

+ 44 - 44
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java

@@ -5,48 +5,48 @@ import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 
 public enum RotationOrder {
-	
-	EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ;
-	
-	// Static values field for fast access by ordinal without Enum.values() overhead
-	public static final RotationOrder[] values = values();
-	
-	private RotationOrder() {
-	}
-	
-	public Quaternion rotate(Vector3f vec) {
-		return fromEuler(vec.x * FastMath.DEG_TO_RAD, vec.y * FastMath.DEG_TO_RAD, vec.z * FastMath.DEG_TO_RAD, this);
-	}
-	
-	public Quaternion rotate(float x, float y, float z) {
-		return fromEuler(x * FastMath.DEG_TO_RAD, y * FastMath.DEG_TO_RAD, z * FastMath.DEG_TO_RAD, this);
-	}
-	
-	private static Quaternion fromEuler(float x, float y, float z, RotationOrder order) {
-		switch(order) {
-		case EULER_XYZ:
-			return toQuat(x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z);
-		case EULER_YXZ:
-			return toQuat(y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z);
-		case EULER_ZXY:
-			return toQuat(z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y);
-		case EULER_ZYX:
-			return toQuat(z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X);
-		case EULER_YZX:
-			return toQuat(y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X);
-		case EULER_XZY:
-			return toQuat(x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y);
-		case SPHERIC_XYZ:
-		default:
-			throw new IllegalArgumentException("Spherical 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
-		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);
-	}
+
+    EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ;
+
+    // Static values field for fast access by ordinal without Enum.values() overhead
+    public static final RotationOrder[] values = values();
+
+    private RotationOrder() {
+    }
+
+    public Quaternion rotate(Vector3f vec) {
+        return fromEuler(vec.x * FastMath.DEG_TO_RAD, vec.y * FastMath.DEG_TO_RAD, vec.z * FastMath.DEG_TO_RAD, this);
+    }
+
+    public Quaternion rotate(float x, float y, float z) {
+        return fromEuler(x * FastMath.DEG_TO_RAD, y * FastMath.DEG_TO_RAD, z * FastMath.DEG_TO_RAD, this);
+    }
+
+    private static Quaternion fromEuler(float x, float y, float z, RotationOrder order) {
+        switch(order) {
+        case EULER_XYZ:
+            return toQuat(x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z);
+        case EULER_YXZ:
+            return toQuat(y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z);
+        case EULER_ZXY:
+            return toQuat(z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y);
+        case EULER_ZYX:
+            return toQuat(z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X);
+        case EULER_YZX:
+            return toQuat(y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X);
+        case EULER_XZY:
+            return toQuat(x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y);
+        case SPHERIC_XYZ:
+        default:
+            throw new IllegalArgumentException("Spherical 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
+        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);
+    }
 }

+ 17 - 17
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java

@@ -34,21 +34,21 @@ package com.jme3.scene.plugins.fbx;
 import com.jme3.asset.ModelKey;
 
 public class SceneKey extends ModelKey {
-	
-	private final AnimationList animList;
-	
-	public SceneKey(String name) {
-		super(name);
-		this.animList = null;
-	}
-	
-	public SceneKey(String name, AnimationList animationList) {
-		super(name);
-		this.animList = animationList;
-	}
-	
-	public AnimationList getAnimations() {
-		return this.animList;
-	}
-	
+
+    private final AnimationList animList;
+
+    public SceneKey(String name) {
+        super(name);
+        this.animList = null;
+    }
+
+    public SceneKey(String name, AnimationList animationList) {
+        super(name);
+        this.animList = animationList;
+    }
+
+    public AnimationList getAnimations() {
+        return this.animList;
+    }
+
 }

+ 424 - 424
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java

@@ -48,428 +48,428 @@ import com.jme3.scene.plugins.fbx.objects.FbxTexture;
  * or loaded from different animation layer.</p>
  */
 public class SceneLoader implements AssetLoader {
-	
-	private static final Logger logger = Logger.getLogger(SceneLoader.class.getName());
-	private static final double secondsPerUnit = 1 / 46186158000d; // Animation speed factor
-	public static final boolean WARN_IGNORED_ATTRIBUTES = false;
-	
-	// Scene loading data
-	private List<String> warnings = new ArrayList<>();
-	private AnimationList animList;
-	private String sceneName;
-	public String sceneFilename;
-	public String sceneFolderName;
-	public AssetManager assetManager;
-	public AssetInfo currentAssetInfo;
-	
-	// Scene global settings
-	private float animFrameRate;
-	public float unitSize;
-	public int xAxis = 1;
-	public int yAxis = 1;
-	public int zAxis = 1;
-	
-	// Scene objects
-	private Map<Long, FbxObject> allObjects = new HashMap<>(); // All supported FBX objects
-	private Map<Long, FbxSkin> skinMap = new HashMap<>(); // Skin for bone clusters
-	private Map<Long, FbxObject> aLayerMap = new HashMap<>(); // Animation layers
-	public Map<Long, FbxNode> modelMap = new HashMap<>(); // Nodes
-	private Map<Long, FbxNode> limbMap = new HashMap<>(); // Nodes that are actually bones
-	private Map<Long, FbxBindPose> bindMap = new HashMap<>(); // Node bind poses
-	private Map<Long, FbxMesh> geomMap = new HashMap<>(); // Mesh geometries
-	private Skeleton skeleton;
-	private AnimControl animControl;
-	public Node sceneNode;
-	
-	public void warning(String warning) {
-		warnings.add(warning);
-	}
-	
-	@Override
-	public Object load(AssetInfo assetInfo) throws IOException {
-		this.currentAssetInfo = assetInfo;
-		this.assetManager = assetInfo.getManager();
-		AssetKey<?> assetKey = assetInfo.getKey();
-		if(assetKey instanceof SceneKey)
-			animList = ((SceneKey) assetKey).getAnimations();
-		InputStream stream = assetInfo.openStream();
-		final Node sceneNode = this.sceneNode = new Node(sceneName + "-scene");
-		try {
-			sceneFilename = assetKey.getName();
-			sceneFolderName = assetKey.getFolder();
-			String ext = assetKey.getExtension();
-			sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1);
-			if(sceneFolderName != null && sceneFolderName.length() > 0)
-				sceneName = sceneName.substring(sceneFolderName.length());
-			loadScene(stream);
-			linkScene();
-			if(warnings.size() > 0)
-				logger.log(Level.WARNING, "Model load finished with warnings:\n" + join(warnings, "\n"));
-		} finally {
-			releaseObjects();
-			if(stream != null)
-				stream.close();
-		}
-		return sceneNode;
-	}
-	
-	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) {
-			// Is it possible for elements to be in wrong order?
-			switch(e.id) {
-			case "GlobalSettings":
-				loadGlobalSettings(e);
-				break;
-			case "Objects":
-				loadObjects(e);
-				break;
-			case "Connections":
-				loadConnections(e);
-				break;
-			}
-		}
-		long estimatedTime = System.currentTimeMillis() - startTime;
-		logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime);
-	}
-	
-	private void loadGlobalSettings(FbxElement element) {
-		for(FbxElement e2 : element.getFbxProperties()) {
-			String propName = (String) e2.properties.get(0);
-			switch(propName) {
-			case "UnitScaleFactor":
-				this.unitSize = ((Double) e2.properties.get(4)).floatValue();
-				break;
-			case "CustomFrameRate":
-				float framerate = ((Double) e2.properties.get(4)).floatValue();
-				if(framerate != -1)
-					this.animFrameRate = framerate;
-				break;
-			case "UpAxisSign":
-				this.yAxis = ((Integer) e2.properties.get(4)).intValue();
-				break;
-			case "FrontAxisSign":
-				this.zAxis = ((Integer) e2.properties.get(4)).intValue();
-				break;
-			case "CoordAxisSign":
-				this.xAxis = ((Integer) e2.properties.get(4)).intValue();
-				break;
-			}
-		}
-	}
-	
-	private void loadObjects(FbxElement element) throws IOException {
-		FbxObject obj = null;
-		for(FbxElement e : element.children) {
-			switch(e.id) {
-			case "Geometry":
-				FbxMesh mesh = new FbxMesh(this, e);
-				obj = mesh;
-				if(mesh.geometries != null)
-					geomMap.put(mesh.id, mesh);
-				break;
-			case "Material":
-				obj = new FbxMaterial(this, e);
-				break;
-			case "Model":
-				FbxNode node = new FbxNode(this, e);
-				obj = node;
-				modelMap.put(node.id, node);
-				if(node.isLimb())
-					limbMap.put(node.id, node);
-				break;
-			case "Pose":
-				FbxBindPose pose = new FbxBindPose(this, e);
-				obj = pose;
-				bindMap.put(pose.id, pose);
-				break;
-			case "Texture":
-				obj = new FbxTexture(this, e);
-				break;
-			case "Video":
-				obj = new FbxImage(this, e);
-				break;
-			case "Deformer":
-				obj = loadDeformer(e);
-				break;
-			case "AnimationLayer":
-				FbxObject layer = new FbxObject(this, e);
-				obj = layer;
-				aLayerMap.put(layer.id, layer);
-				break;
-			case "AnimationCurve":
-				obj = new FbxAnimCurve(this, e);
-				break;
-			case "AnimationCurveNode":
-				obj = new FbxAnimNode(this, e);
-				break;
-			default:
-				obj = null;
-				//warnings.add("Object with id '" + e.id + "' was ignored");
-			}
-			if(obj != null)
-				allObjects.put(obj.id, obj);
-		}
-	}
-	
-	private FbxObject loadDeformer(FbxElement element) {
-		String type = (String) element.properties.get(2);
-		switch(type) {
-		case "Skin":
-			FbxSkin skinData = new FbxSkin(this, element);
-			skinMap.put(skinData.id, skinData);
-			return skinData;
-		case "Cluster":
-			FbxCluster clusterData = new FbxCluster(this, element);
-			return clusterData;
-		}
-		return null;
-	}
-	
-	private void loadConnections(FbxElement element) {
-		for(FbxElement e : element.children) {
-			if(e.id.equals("C")) {
-				String type = (String) e.properties.get(0);
-				long objId, refId;
-				FbxObject obj, ref;
-				switch(type) {
-				case "OO":
-					objId = (Long) e.properties.get(1);
-					refId = (Long) e.properties.get(2);
-					obj = allObjects.get(objId);
-					ref = allObjects.get(refId);
-					if(ref != null) {
-						ref.link(obj);
-					} else if(refId == 0) {
-						obj.linkToZero();
-					}
-					break;
-				case "OP":
-					objId = (Long) e.properties.get(1);
-					refId = (Long) e.properties.get(2);
-					String propName = (String) e.properties.get(3);
-					obj = allObjects.get(objId);
-					ref = allObjects.get(refId);
-					if(ref != null)
-						ref.link(obj, propName);
-					break;
-				}
-			}
-		}
-	}
-	
-	private void linkScene() {
-		logger.log(Level.FINE, "Linking scene objects");
-		long startTime = System.currentTimeMillis();
-		applySkinning();
-		buildAnimations();
-		for(FbxMesh mesh : geomMap.values())
-			mesh.clearMaterials();
-		// Remove bones from node structures : JME creates attach node by itself
-		for(FbxNode limb : limbMap.values())
-			limb.node.removeFromParent();
-		long estimatedTime = System.currentTimeMillis() - startTime;
-		logger.log(Level.FINE, "Linking done in {0} ms", estimatedTime);
-	}
-	
-	private void applySkinning() {
-		for(FbxBindPose pose : bindMap.values())
-			pose.fillBindTransforms();
-		if(limbMap == null)
-			return;
-		List<Bone> bones = new ArrayList<>();
-		for(FbxNode limb : limbMap.values()) {
-			if(limb.bone != null) {
-				bones.add(limb.bone);
-				limb.buildBindPoseBoneTransform();
-			}
-		}
-		skeleton = new Skeleton(bones.toArray(new Bone[bones.size()]));
-		skeleton.setBindingPose();
-		for(FbxNode limb : limbMap.values())
-			limb.setSkeleton(skeleton);
-		for(FbxSkin skin : skinMap.values())
-			skin.generateSkinning();
-		// Attach controls
-		animControl = new AnimControl(skeleton);
-		sceneNode.addControl(animControl);
-		SkeletonControl control = new SkeletonControl(skeleton);
-		sceneNode.addControl(control);
-	}
-	
-	private void buildAnimations() {
-		if(skeleton == null)
-			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);
-			}
-		}
-		// Extract animations
-		HashMap<String, Animation> anims = new HashMap<>();
-		for(AnimInverval animInfo : animList.list) {
-			float realLength = 0;
-			float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate;
-			float animStart = animInfo.firstFrame / this.animFrameRate;
-			float animStop = animInfo.lastFrame / this.animFrameRate;
-			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<Long> 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) (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) (keyTimes[keyIndex] * secondsPerUnit); // Translate into seconds
-					times[i] = time - animStart;
-					realLength = Math.max(realLength, times[i]);
-				}
-				// Load keyframes from animation curves
-				if(haveTranslation) {
-					for(int i = 0; i < keysCount; ++i) {
-						int keyIndex = firstKeyIndex + i;
-						FbxAnimNode n = animTranslation;
-						Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value).subtractLocal(n.value); // Why do it here but not in other places? FBX magic?
-						translations[i] = tvec.divideLocal(unitSize);
-					}
-				} else {
-					for(int i = 0; i < keysCount; ++i)
-						translations[i] = 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);
-					}
-				} else {
-					for(int i = 0; i < keysCount; ++i)
-						rotations[i] = Quaternion.IDENTITY;
-				}
-				if(haveScale) {
-					scales = new Vector3f[keysCount];
-					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;
-					}
-				}
-				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();
-				if(tracks == null || tracks.length == 0)
-					continue;
-				anim = new Animation(animInfo.name, realLength);
-				for(Track track : tracks)
-					anim.addTrack(track);
-			}
-			anims.put(anim.getName(), anim);
-		}
-		animControl.setAnimations(anims);
-	}
-	
-	private void releaseObjects() {
-		// Reset settings
-		unitSize = 1;
-		animFrameRate = 30;
-		xAxis = 1;
-		yAxis = 1;
-		zAxis = 1;
-		// Clear cache
-		warnings.clear();
-		animList = null;
-		sceneName = null;
-		sceneFilename = null;
-		sceneFolderName = null;
-		assetManager = null;
-		currentAssetInfo = null;
-		// Clear objects
-		allObjects.clear();
-		skinMap.clear();
-		aLayerMap.clear();
-		modelMap.clear();
-		limbMap.clear();
-		bindMap.clear();
-		geomMap.clear();
-		skeleton = null;
-		animControl = null;
-		sceneNode = null;
-	}
-	
-	
-	private static boolean haveAnyChannel(FbxAnimNode anims) {
-		return anims != null && anims.haveAnyChannel();
-	}
-	
-	private static String join(List<String> 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();
-	}
+
+    private static final Logger logger = Logger.getLogger(SceneLoader.class.getName());
+    private static final double secondsPerUnit = 1 / 46186158000d; // Animation speed factor
+    public static final boolean WARN_IGNORED_ATTRIBUTES = false;
+
+    // Scene loading data
+    private List<String> warnings = new ArrayList<>();
+    private AnimationList animList;
+    private String sceneName;
+    public String sceneFilename;
+    public String sceneFolderName;
+    public AssetManager assetManager;
+    public AssetInfo currentAssetInfo;
+
+    // Scene global settings
+    private float animFrameRate;
+    public float unitSize;
+    public int xAxis = 1;
+    public int yAxis = 1;
+    public int zAxis = 1;
+
+    // Scene objects
+    private Map<Long, FbxObject> allObjects = new HashMap<>(); // All supported FBX objects
+    private Map<Long, FbxSkin> skinMap = new HashMap<>(); // Skin for bone clusters
+    private Map<Long, FbxObject> aLayerMap = new HashMap<>(); // Animation layers
+    public Map<Long, FbxNode> modelMap = new HashMap<>(); // Nodes
+    private Map<Long, FbxNode> limbMap = new HashMap<>(); // Nodes that are actually bones
+    private Map<Long, FbxBindPose> bindMap = new HashMap<>(); // Node bind poses
+    private Map<Long, FbxMesh> geomMap = new HashMap<>(); // Mesh geometries
+    private Skeleton skeleton;
+    private AnimControl animControl;
+    public Node sceneNode;
+
+    public void warning(String warning) {
+        warnings.add(warning);
+    }
+
+    @Override
+    public Object load(AssetInfo assetInfo) throws IOException {
+        this.currentAssetInfo = assetInfo;
+        this.assetManager = assetInfo.getManager();
+        AssetKey<?> assetKey = assetInfo.getKey();
+        if(assetKey instanceof SceneKey)
+            animList = ((SceneKey) assetKey).getAnimations();
+        InputStream stream = assetInfo.openStream();
+        final Node sceneNode = this.sceneNode = new Node(sceneName + "-scene");
+        try {
+            sceneFilename = assetKey.getName();
+            sceneFolderName = assetKey.getFolder();
+            String ext = assetKey.getExtension();
+            sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1);
+            if(sceneFolderName != null && sceneFolderName.length() > 0)
+                sceneName = sceneName.substring(sceneFolderName.length());
+            loadScene(stream);
+            linkScene();
+            if(warnings.size() > 0)
+                logger.log(Level.WARNING, "Model load finished with warnings:\n" + join(warnings, "\n"));
+        } finally {
+            releaseObjects();
+            if(stream != null)
+                stream.close();
+        }
+        return sceneNode;
+    }
+
+    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) {
+            // Is it possible for elements to be in wrong order?
+            switch(e.id) {
+            case "GlobalSettings":
+                loadGlobalSettings(e);
+                break;
+            case "Objects":
+                loadObjects(e);
+                break;
+            case "Connections":
+                loadConnections(e);
+                break;
+            }
+        }
+        long estimatedTime = System.currentTimeMillis() - startTime;
+        logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime);
+    }
+
+    private void loadGlobalSettings(FbxElement element) {
+        for(FbxElement e2 : element.getFbxProperties()) {
+            String propName = (String) e2.properties.get(0);
+            switch(propName) {
+            case "UnitScaleFactor":
+                this.unitSize = ((Double) e2.properties.get(4)).floatValue();
+                break;
+            case "CustomFrameRate":
+                float framerate = ((Double) e2.properties.get(4)).floatValue();
+                if(framerate != -1)
+                    this.animFrameRate = framerate;
+                break;
+            case "UpAxisSign":
+                this.yAxis = ((Integer) e2.properties.get(4)).intValue();
+                break;
+            case "FrontAxisSign":
+                this.zAxis = ((Integer) e2.properties.get(4)).intValue();
+                break;
+            case "CoordAxisSign":
+                this.xAxis = ((Integer) e2.properties.get(4)).intValue();
+                break;
+            }
+        }
+    }
+
+    private void loadObjects(FbxElement element) throws IOException {
+        FbxObject obj = null;
+        for(FbxElement e : element.children) {
+            switch(e.id) {
+            case "Geometry":
+                FbxMesh mesh = new FbxMesh(this, e);
+                obj = mesh;
+                if(mesh.geometries != null)
+                    geomMap.put(mesh.id, mesh);
+                break;
+            case "Material":
+                obj = new FbxMaterial(this, e);
+                break;
+            case "Model":
+                FbxNode node = new FbxNode(this, e);
+                obj = node;
+                modelMap.put(node.id, node);
+                if(node.isLimb())
+                    limbMap.put(node.id, node);
+                break;
+            case "Pose":
+                FbxBindPose pose = new FbxBindPose(this, e);
+                obj = pose;
+                bindMap.put(pose.id, pose);
+                break;
+            case "Texture":
+                obj = new FbxTexture(this, e);
+                break;
+            case "Video":
+                obj = new FbxImage(this, e);
+                break;
+            case "Deformer":
+                obj = loadDeformer(e);
+                break;
+            case "AnimationLayer":
+                FbxObject layer = new FbxObject(this, e);
+                obj = layer;
+                aLayerMap.put(layer.id, layer);
+                break;
+            case "AnimationCurve":
+                obj = new FbxAnimCurve(this, e);
+                break;
+            case "AnimationCurveNode":
+                obj = new FbxAnimNode(this, e);
+                break;
+            default:
+                obj = null;
+                //warnings.add("Object with id '" + e.id + "' was ignored");
+            }
+            if(obj != null)
+                allObjects.put(obj.id, obj);
+        }
+    }
+
+    private FbxObject loadDeformer(FbxElement element) {
+        String type = (String) element.properties.get(2);
+        switch(type) {
+        case "Skin":
+            FbxSkin skinData = new FbxSkin(this, element);
+            skinMap.put(skinData.id, skinData);
+            return skinData;
+        case "Cluster":
+            FbxCluster clusterData = new FbxCluster(this, element);
+            return clusterData;
+        }
+        return null;
+    }
+
+    private void loadConnections(FbxElement element) {
+        for(FbxElement e : element.children) {
+            if(e.id.equals("C")) {
+                String type = (String) e.properties.get(0);
+                long objId, refId;
+                FbxObject obj, ref;
+                switch(type) {
+                case "OO":
+                    objId = (Long) e.properties.get(1);
+                    refId = (Long) e.properties.get(2);
+                    obj = allObjects.get(objId);
+                    ref = allObjects.get(refId);
+                    if(ref != null) {
+                        ref.link(obj);
+                    } else if(refId == 0) {
+                        obj.linkToZero();
+                    }
+                    break;
+                case "OP":
+                    objId = (Long) e.properties.get(1);
+                    refId = (Long) e.properties.get(2);
+                    String propName = (String) e.properties.get(3);
+                    obj = allObjects.get(objId);
+                    ref = allObjects.get(refId);
+                    if(ref != null)
+                        ref.link(obj, propName);
+                    break;
+                }
+            }
+        }
+    }
+
+    private void linkScene() {
+        logger.log(Level.FINE, "Linking scene objects");
+        long startTime = System.currentTimeMillis();
+        applySkinning();
+        buildAnimations();
+        for(FbxMesh mesh : geomMap.values())
+            mesh.clearMaterials();
+        // Remove bones from node structures : JME creates attach node by itself
+        for(FbxNode limb : limbMap.values())
+            limb.node.removeFromParent();
+        long estimatedTime = System.currentTimeMillis() - startTime;
+        logger.log(Level.FINE, "Linking done in {0} ms", estimatedTime);
+    }
+
+    private void applySkinning() {
+        for(FbxBindPose pose : bindMap.values())
+            pose.fillBindTransforms();
+        if(limbMap == null)
+            return;
+        List<Bone> bones = new ArrayList<>();
+        for(FbxNode limb : limbMap.values()) {
+            if(limb.bone != null) {
+                bones.add(limb.bone);
+                limb.buildBindPoseBoneTransform();
+            }
+        }
+        skeleton = new Skeleton(bones.toArray(new Bone[bones.size()]));
+        skeleton.setBindingPose();
+        for(FbxNode limb : limbMap.values())
+            limb.setSkeleton(skeleton);
+        for(FbxSkin skin : skinMap.values())
+            skin.generateSkinning();
+        // Attach controls
+        animControl = new AnimControl(skeleton);
+        sceneNode.addControl(animControl);
+        SkeletonControl control = new SkeletonControl(skeleton);
+        sceneNode.addControl(control);
+    }
+
+    private void buildAnimations() {
+        if(skeleton == null)
+            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);
+            }
+        }
+        // Extract animations
+        HashMap<String, Animation> anims = new HashMap<>();
+        for(AnimInverval animInfo : animList.list) {
+            float realLength = 0;
+            float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate;
+            float animStart = animInfo.firstFrame / this.animFrameRate;
+            float animStop = animInfo.lastFrame / this.animFrameRate;
+            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<Long> 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) (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) (keyTimes[keyIndex] * secondsPerUnit); // Translate into seconds
+                    times[i] = time - animStart;
+                    realLength = Math.max(realLength, times[i]);
+                }
+                // Load keyframes from animation curves
+                if(haveTranslation) {
+                    for(int i = 0; i < keysCount; ++i) {
+                        int keyIndex = firstKeyIndex + i;
+                        FbxAnimNode n = animTranslation;
+                        Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value).subtractLocal(n.value); // Why do it here but not in other places? FBX magic?
+                        translations[i] = tvec.divideLocal(unitSize);
+                    }
+                } else {
+                    for(int i = 0; i < keysCount; ++i)
+                        translations[i] = 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);
+                    }
+                } else {
+                    for(int i = 0; i < keysCount; ++i)
+                        rotations[i] = Quaternion.IDENTITY;
+                }
+                if(haveScale) {
+                    scales = new Vector3f[keysCount];
+                    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;
+                    }
+                }
+                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();
+                if(tracks == null || tracks.length == 0)
+                    continue;
+                anim = new Animation(animInfo.name, realLength);
+                for(Track track : tracks)
+                    anim.addTrack(track);
+            }
+            anims.put(anim.getName(), anim);
+        }
+        animControl.setAnimations(anims);
+    }
+
+    private void releaseObjects() {
+        // Reset settings
+        unitSize = 1;
+        animFrameRate = 30;
+        xAxis = 1;
+        yAxis = 1;
+        zAxis = 1;
+        // Clear cache
+        warnings.clear();
+        animList = null;
+        sceneName = null;
+        sceneFilename = null;
+        sceneFolderName = null;
+        assetManager = null;
+        currentAssetInfo = null;
+        // Clear objects
+        allObjects.clear();
+        skinMap.clear();
+        aLayerMap.clear();
+        modelMap.clear();
+        limbMap.clear();
+        bindMap.clear();
+        geomMap.clear();
+        skeleton = null;
+        animControl = null;
+        sceneNode = null;
+    }
+
+
+    private static boolean haveAnyChannel(FbxAnimNode anims) {
+        return anims != null && anims.haveAnyChannel();
+    }
+
+    private static String join(List<String> 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();
+    }
 }

+ 49 - 49
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneWithAnimationLoader.java

@@ -15,53 +15,53 @@ import com.jme3.asset.AssetLoader;
 import com.jme3.asset.ModelKey;
 
 public class SceneWithAnimationLoader implements AssetLoader {
-	
-	private Pattern splitStrings = Pattern.compile("([^\"]\\S*|\".+?\")\\s*");
-	
-	@Override
-	public Object load(AssetInfo assetInfo) throws IOException {
-		AssetKey<?> key = assetInfo.getKey();
-		if(!(key instanceof ModelKey))
-			throw new AssetLoadException("Invalid asset key");
-		InputStream stream = assetInfo.openStream();
-		Scanner scanner = new Scanner(stream);
-		AnimationList animList = new AnimationList();
-		String modelName = null;
-		try {
-			while(scanner.hasNextLine()) {
-				String line = scanner.nextLine();
-				if(line.startsWith("#"))
-					continue;
-				if(modelName == null) {
-					modelName = line;
-					continue;
-				}
-				String[] split = split(line);
-				if(split.length < 3)
-					throw new IOException("Unparseable string \"" + line + "\"");
-				int start;
-				int end;
-				try {
-					start = Integer.parseInt(split[0]);
-					end = Integer.parseInt(split[1]);
-				} catch(NumberFormatException e) {
-					throw new IOException("Unparseable string \"" + line + "\"", e);
-				}
-				animList.add(split[2], split.length > 3 ? split[3] : null, start, end);
-			}
-		} finally {
-			scanner.close();
-			stream.close();
-		}
-		return assetInfo.getManager().loadAsset(new SceneKey(key.getFolder() + modelName, animList));
-	}
-	
-	private String[] split(String src) {
-		List<String> list = new ArrayList<>();
-		Matcher m = splitStrings.matcher(src);
-		while(m.find())
-			list.add(m.group(1).replace("\"", ""));
-		return list.toArray(new String[list.size()]);
-	}
-	
+
+    private Pattern splitStrings = Pattern.compile("([^\"]\\S*|\".+?\")\\s*");
+
+    @Override
+    public Object load(AssetInfo assetInfo) throws IOException {
+        AssetKey<?> key = assetInfo.getKey();
+        if(!(key instanceof ModelKey))
+            throw new AssetLoadException("Invalid asset key");
+        InputStream stream = assetInfo.openStream();
+        Scanner scanner = new Scanner(stream);
+        AnimationList animList = new AnimationList();
+        String modelName = null;
+        try {
+            while(scanner.hasNextLine()) {
+                String line = scanner.nextLine();
+                if(line.startsWith("#"))
+                    continue;
+                if(modelName == null) {
+                    modelName = line;
+                    continue;
+                }
+                String[] split = split(line);
+                if(split.length < 3)
+                    throw new IOException("Unparseable string \"" + line + "\"");
+                int start;
+                int end;
+                try {
+                    start = Integer.parseInt(split[0]);
+                    end = Integer.parseInt(split[1]);
+                } catch(NumberFormatException e) {
+                    throw new IOException("Unparseable string \"" + line + "\"", e);
+                }
+                animList.add(split[2], split.length > 3 ? split[3] : null, start, end);
+            }
+        } finally {
+            scanner.close();
+            stream.close();
+        }
+        return assetInfo.getManager().loadAsset(new SceneKey(key.getFolder() + modelName, animList));
+    }
+
+    private String[] split(String src) {
+        List<String> list = new ArrayList<>();
+        Matcher m = splitStrings.matcher(src);
+        while(m.find())
+            list.add(m.group(1).replace("\"", ""));
+        return list.toArray(new String[list.size()]);
+    }
+
 }

+ 26 - 26
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java

@@ -35,32 +35,32 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class FbxElement {
-	
-	public String id;
-	public List<Object> properties;
-	/*
-	 * Y - signed short
-	 * C - boolean
-	 * I - signed integer
-	 * F - float
-	 * D - double
-	 * L - long
-	 * R - byte array
-	 * S - string
-	 * f - array of floats
-	 * i - array of ints
-	 * d - array of doubles
-	 * l - array of longs
-	 * b - array of booleans
-	 * c - array of unsigned bytes (represented as array of ints)
-	 */
-	public char[] propertiesTypes;
-	public List<FbxElement> children = new ArrayList<>();
-	
-	public FbxElement(int propsCount) {
-		this.properties = new ArrayList<Object>(propsCount);
-		this.propertiesTypes = new char[propsCount];
-	}
+
+    public String id;
+    public List<Object> properties;
+    /*
+     * Y - signed short
+     * C - boolean
+     * I - signed integer
+     * F - float
+     * D - double
+     * L - long
+     * R - byte array
+     * S - string
+     * f - array of floats
+     * i - array of ints
+     * d - array of doubles
+     * l - array of longs
+     * b - array of booleans
+     * c - array of unsigned bytes (represented as array of ints)
+     */
+    public char[] propertiesTypes;
+    public List<FbxElement> children = new ArrayList<>();
+
+    public FbxElement(int propsCount) {
+        this.properties = new ArrayList<Object>(propsCount);
+        this.propertiesTypes = new char[propsCount];
+    }
         
         public FbxElement getChildById(String name) {
             for (FbxElement element : children) {

+ 8 - 8
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxFile.java

@@ -35,12 +35,12 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class FbxFile {
-	
-	public List<FbxElement> rootElements = new ArrayList<>();
-	public long version;
-	
-        @Override
-        public String toString() {
-            return "FBXFile[version=" + version + ",numElements=" + rootElements.size() + "]";
-        }
+
+    public List<FbxElement> rootElements = new ArrayList<>();
+    public long version;
+
+    @Override
+    public String toString() {
+        return "FBXFile[version=" + version + ",numElements=" + rootElements.size() + "]";
+    }
 }

+ 183 - 183
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxReader.java

@@ -42,192 +42,192 @@ import java.util.zip.InflaterInputStream;
 
 public class FbxReader {
 
-	public static final int BLOCK_SENTINEL_LENGTH = 13;
-	public static final byte[] BLOCK_SENTINEL_DATA = new byte[BLOCK_SENTINEL_LENGTH];
-	/**
-	 * magic 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 BLOCK_SENTINEL_LENGTH = 13;
+    public static final byte[] BLOCK_SENTINEL_DATA = new byte[BLOCK_SENTINEL_LENGTH];
+    /**
+     * magic 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};
 
-        /**
-         * A private constructor to inhibit instantiation of this class.
-         */
-        private FbxReader() {
-        }
+    /**
+     * A private constructor to inhibit instantiation of this class.
+     */
+    private FbxReader() {
+    }
 
-	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
-		ByteBuffer byteBuffer = readToByteBuffer(stream);
-		try {
-			stream.close();
-		} catch(IOException e) {
-		}
-		// Check magic header
-		byte[] magic = getBytes(byteBuffer, HEAD_MAGIC.length);
-		if(!Arrays.equals(HEAD_MAGIC, magic))
-			throw new IOException("Either ASCII FBX or corrupt file. "
+    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
+        ByteBuffer byteBuffer = readToByteBuffer(stream);
+        try {
+            stream.close();
+        } catch(IOException e) {
+        }
+        // Check magic header
+        byte[] magic = getBytes(byteBuffer, HEAD_MAGIC.length);
+        if(!Arrays.equals(HEAD_MAGIC, magic))
+            throw new IOException("Either ASCII FBX or corrupt file. "
                                             + "Only binary FBX files are supported");
 
-		// Read version
-		fbxFile.version = getUInt(byteBuffer);
-		// Read root elements
-		while(true) {
-			FbxElement e = readFBXElement(byteBuffer);
-			if(e == null)
-				break;
-			fbxFile.rootElements.add(e);
-		}
-		return fbxFile;
-	}
+        // Read version
+        fbxFile.version = getUInt(byteBuffer);
+        // Read root elements
+        while(true) {
+            FbxElement e = readFBXElement(byteBuffer);
+            if(e == null)
+                break;
+            fbxFile.rootElements.add(e);
+        }
+        return fbxFile;
+    }
+
+    private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOException {
+        long endOffset = getUInt(byteBuffer);
+        if(endOffset == 0)
+            return null;
+        long propCount = getUInt(byteBuffer);
+        getUInt(byteBuffer); // Properties length unused
+        
+        FbxElement element = new FbxElement((int) propCount);
+        element.id = new String(getBytes(byteBuffer, getUByte(byteBuffer)));
+        
+        for(int i = 0; i < propCount; ++i) {
+            char dataType = readDataType(byteBuffer);
+            element.properties.add(readData(byteBuffer, dataType));
+            element.propertiesTypes[i] = dataType;
+        }
+        if(byteBuffer.position() < endOffset) {
+            while(byteBuffer.position() < (endOffset - BLOCK_SENTINEL_LENGTH))
+                element.children.add(readFBXElement(byteBuffer));
+            
+            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");
+        return element;
+    }
+
+    private static Object readData(ByteBuffer byteBuffer, char dataType) throws IOException {
+        switch(dataType) {
+        case 'Y':
+            return byteBuffer.getShort();
+        case 'C':
+            return byteBuffer.get() == 1;
+        case 'I':
+            return byteBuffer.getInt();
+        case 'F':
+            return byteBuffer.getFloat();
+        case 'D':
+            return byteBuffer.getDouble();
+        case 'L':
+            return byteBuffer.getLong();
+        case 'R':
+            return getBytes(byteBuffer, (int) getUInt(byteBuffer));
+        case 'S':
+            return new String(getBytes(byteBuffer, (int) getUInt(byteBuffer)));
+        case 'f':
+            return readArray(byteBuffer, 'f', 4);
+        case 'i':
+            return readArray(byteBuffer, 'i', 4);
+        case 'd':
+            return readArray(byteBuffer, 'd', 8);
+        case 'l':
+            return readArray(byteBuffer, 'l', 8);
+        case 'b':
+            return readArray(byteBuffer, 'b', 1);
+        case 'c':
+            return readArray(byteBuffer, 'c', 1);
+        }
+        throw new IOException("Unknown data type: " + dataType);
+    }
+
+    private static Object readArray(ByteBuffer byteBuffer, char type, int bytes) throws IOException {
+        int count = (int) getUInt(byteBuffer);
+        int encoding = (int) getUInt(byteBuffer);
+        int length = (int) getUInt(byteBuffer);
 
-	private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOException {
-		long endOffset = getUInt(byteBuffer);
-		if(endOffset == 0)
-			return null;
-		long propCount = getUInt(byteBuffer);
-		getUInt(byteBuffer); // Properties length unused
-		
-		FbxElement element = new FbxElement((int) propCount);
-		element.id = new String(getBytes(byteBuffer, getUByte(byteBuffer)));
-		
-		for(int i = 0; i < propCount; ++i) {
-			char dataType = readDataType(byteBuffer);
-			element.properties.add(readData(byteBuffer, dataType));
-			element.propertiesTypes[i] = dataType;
-		}
-		if(byteBuffer.position() < endOffset) {
-			while(byteBuffer.position() < (endOffset - BLOCK_SENTINEL_LENGTH))
-				element.children.add(readFBXElement(byteBuffer));
-			
-			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");
-		return element;
-	}
-	
-	private static Object readData(ByteBuffer byteBuffer, char dataType) throws IOException {
-		switch(dataType) {
-		case 'Y':
-			return byteBuffer.getShort();
-		case 'C':
-			return byteBuffer.get() == 1;
-		case 'I':
-			return byteBuffer.getInt();
-		case 'F':
-			return byteBuffer.getFloat();
-		case 'D':
-			return byteBuffer.getDouble();
-		case 'L':
-			return byteBuffer.getLong();
-		case 'R':
-			return getBytes(byteBuffer, (int) getUInt(byteBuffer));
-		case 'S':
-			return new String(getBytes(byteBuffer, (int) getUInt(byteBuffer)));
-		case 'f':
-			return readArray(byteBuffer, 'f', 4);
-		case 'i':
-			return readArray(byteBuffer, 'i', 4);
-		case 'd':
-			return readArray(byteBuffer, 'd', 8);
-		case 'l':
-			return readArray(byteBuffer, 'l', 8);
-		case 'b':
-			return readArray(byteBuffer, 'b', 1);
-		case 'c':
-			return readArray(byteBuffer, 'c', 1);
-		}
-		throw new IOException("Unknown data type: " + dataType);
-	}
-	
-	private static Object readArray(ByteBuffer byteBuffer, char type, int bytes) throws IOException {
-		int count = (int) getUInt(byteBuffer);
-		int encoding = (int) getUInt(byteBuffer);
-		int length = (int) getUInt(byteBuffer);
-		
-		byte[] data = getBytes(byteBuffer, length);
-		if(encoding == 1)
-			data = inflate(data);
-		if(data.length != count * bytes)
-			throw new IOException("Wrong data length. Expected: " + count * bytes + ", got: " + data.length);
-		ByteBuffer dis = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
-		switch(type) {
-		case 'f':
-			float[] arr = new float[count];
-			for(int i = 0; i < count; ++i)
-				arr[i] = dis.getFloat();
-			return arr;
-		case 'i':
-			int[] arr2 = new int[count];
-			for(int i = 0; i < count; ++i)
-				arr2[i] = dis.getInt();
-			return arr2;
-		case 'd':
-			double[] arr3 = new double[count];
-			for(int i = 0; i < count; ++i)
-				arr3[i] = dis.getDouble();
-			return arr3;
-		case 'l':
-			long[] arr4 = new long[count];
-			for(int i = 0; i < count; ++i)
-				arr4[i] = dis.getLong();
-			return arr4;
-		case 'b':
-			boolean[] arr5 = new boolean[count];
-			for(int i = 0; i < count; ++i)
-				arr5[i] = dis.get() == 1;
-			return arr5;
-		case 'c':
-			int[] arr6 = new int[count];
-			for(int i = 0; i < count; ++i)
-				arr6[i] = dis.get() & 0xFF;
-			return arr6;
-		}
-		throw new IOException("Unknown array data type: " + type);
-	}
-	
-	private static byte[] inflate(byte[] input) throws IOException {
-		InflaterInputStream gzis = new InflaterInputStream(new ByteArrayInputStream(input));
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		byte[] buffer = new byte[1024];
-		while(gzis.available() > 0) {
-			int l = gzis.read(buffer);
-			if(l > 0)
-				out.write(buffer, 0, l);
-		}
-		return out.toByteArray();
-	}
-	
-	private static char readDataType(ByteBuffer byteBuffer) {
-		return (char) byteBuffer.get();
-	}
-	
-	private static long getUInt(ByteBuffer byteBuffer) {
-		return byteBuffer.getInt() & 0x00000000ffffffffL;
-	}
-	
-	private static int getUByte(ByteBuffer byteBuffer) {
-		return byteBuffer.get() & 0xFF;
-	}
-	
-	private static byte[] getBytes(ByteBuffer byteBuffer, int size) {
-		byte[] b = new byte[size];
-		byteBuffer.get(b);
-		return b;
-	}
-	
-	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);
-	}
+        byte[] data = getBytes(byteBuffer, length);
+        if(encoding == 1)
+            data = inflate(data);
+        if(data.length != count * bytes)
+            throw new IOException("Wrong data length. Expected: " + count * bytes + ", got: " + data.length);
+        ByteBuffer dis = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+        switch(type) {
+        case 'f':
+            float[] arr = new float[count];
+            for(int i = 0; i < count; ++i)
+                arr[i] = dis.getFloat();
+            return arr;
+        case 'i':
+            int[] arr2 = new int[count];
+            for(int i = 0; i < count; ++i)
+                arr2[i] = dis.getInt();
+            return arr2;
+        case 'd':
+            double[] arr3 = new double[count];
+            for(int i = 0; i < count; ++i)
+                arr3[i] = dis.getDouble();
+            return arr3;
+        case 'l':
+            long[] arr4 = new long[count];
+            for(int i = 0; i < count; ++i)
+                arr4[i] = dis.getLong();
+            return arr4;
+        case 'b':
+            boolean[] arr5 = new boolean[count];
+            for(int i = 0; i < count; ++i)
+                arr5[i] = dis.get() == 1;
+            return arr5;
+        case 'c':
+            int[] arr6 = new int[count];
+            for(int i = 0; i < count; ++i)
+                arr6[i] = dis.get() & 0xFF;
+            return arr6;
+        }
+        throw new IOException("Unknown array data type: " + type);
+    }
+
+    private static byte[] inflate(byte[] input) throws IOException {
+        InflaterInputStream gzis = new InflaterInputStream(new ByteArrayInputStream(input));
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        while(gzis.available() > 0) {
+            int l = gzis.read(buffer);
+            if(l > 0)
+                out.write(buffer, 0, l);
+        }
+        return out.toByteArray();
+    }
+
+    private static char readDataType(ByteBuffer byteBuffer) {
+        return (char) byteBuffer.get();
+    }
+
+    private static long getUInt(ByteBuffer byteBuffer) {
+        return byteBuffer.getInt() & 0x00000000ffffffffL;
+    }
+    
+    private static int getUByte(ByteBuffer byteBuffer) {
+        return byteBuffer.get() & 0xFF;
+    }
+
+    private static byte[] getBytes(ByteBuffer byteBuffer, int size) {
+        byte[] b = new byte[size];
+        byteBuffer.get(b);
+        return b;
+    }
+
+    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);
+    }
 }

+ 42 - 42
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java

@@ -4,46 +4,46 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxAnimCurve extends FbxObject {
-	
-	public long[] keyTimes;
-	public float[] keyValues;
-	public float defaultValue = 0.0f;
-	
-	public FbxAnimCurve(SceneLoader scene, FbxElement element) {
-		super(scene, element);
-		for(FbxElement e : element.children) {
-			switch(e.id) {
-			case "KeyTime":
-				keyTimes = (long[]) e.properties.get(0);
-				break;
-			case "KeyValueFloat":
-				keyValues = (float[]) e.properties.get(0);
-				break;
-			case "Default":
-				defaultValue = ((Number) e.properties.get(0)).floatValue();
-				break;
-			}
-		}
-	}
-	
-	public float getValue(long time) {
-		// Search animation interval
-		for(int i = 0; i < keyTimes.length; ++i) {
-			if(keyTimes[i] == time) { // hit the keyframe
-				return keyValues[i];
-			} else if(keyTimes[i] > time) {
-				if(i == 0) { // left from the whole range
-					return defaultValue;//keyValues[0];
-				} else {
-					// Interpolate between two keyframes
-					float dt = keyTimes[i] - keyTimes[i - 1];
-					float dtInt = time - keyTimes[i - 1];
-					float dv = keyValues[i] - keyValues[i - 1];
-					return keyValues[i - 1] + dv * (dtInt / dt);
-				}
-			}
-		}
-		// right from the whole range
-		return defaultValue;//keyValues[keyValues.length - 1];
-	}
+
+    public long[] keyTimes;
+    public float[] keyValues;
+    public float defaultValue = 0.0f;
+
+    public FbxAnimCurve(SceneLoader scene, FbxElement element) {
+        super(scene, element);
+        for(FbxElement e : element.children) {
+            switch(e.id) {
+            case "KeyTime":
+                keyTimes = (long[]) e.properties.get(0);
+                break;
+            case "KeyValueFloat":
+                keyValues = (float[]) e.properties.get(0);
+                break;
+            case "Default":
+                defaultValue = ((Number) e.properties.get(0)).floatValue();
+                break;
+            }
+        }
+    }
+
+    public float getValue(long time) {
+        // Search animation interval
+        for(int i = 0; i < keyTimes.length; ++i) {
+            if(keyTimes[i] == time) { // hit the keyframe
+                return keyValues[i];
+            } else if(keyTimes[i] > time) {
+                if(i == 0) { // left from the whole range
+                    return defaultValue;//keyValues[0];
+                } else {
+                    // Interpolate between two keyframes
+                    float dt = keyTimes[i] - keyTimes[i - 1];
+                    float dtInt = time - keyTimes[i - 1];
+                    float dv = keyValues[i] - keyValues[i - 1];
+                    return keyValues[i - 1] + dv * (dtInt / dt);
+                }
+            }
+        }
+        // right from the whole range
+        return defaultValue;//keyValues[keyValues.length - 1];
+    }
 }

+ 76 - 76
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java

@@ -7,80 +7,80 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxAnimNode extends FbxObject {
-	
-	public Vector3f value;
-	public FbxAnimCurve xCurve;
-	public FbxAnimCurve yCurve;
-	public FbxAnimCurve zCurve;
-	public long layerId;
-	
-	public FbxAnimNode(SceneLoader scene, FbxElement element) {
-		super(scene, element);
-		if(type.equals("")) {
-			Double x = null, y = null, z = null;
-			for(FbxElement e2 : element.getFbxProperties()) {
-				String propName = (String) e2.properties.get(0);
-				switch(propName) {
-				case "d|X":
-					x = (Double) e2.properties.get(4);
-					break;
-				case "d|Y":
-					y = (Double) e2.properties.get(4);
-					break;
-				case "d|Z":
-					z = (Double) e2.properties.get(4);
-					break;
-				}
-			}
-			// Load only T R S curve nodes
-			if(x != null && y != null && z != null)
-				value = new Vector3f(x.floatValue(), y.floatValue(), z.floatValue());
-		}
-	}
-	
-	@Override
-	public void link(FbxObject otherObject, String propertyName) {
-		if(otherObject instanceof FbxAnimCurve) {
-			FbxAnimCurve curve = (FbxAnimCurve) otherObject;
-			switch(propertyName) {
-			case "d|X":
-				xCurve = curve;
-				break;
-			case "d|Y":
-				yCurve = curve;
-				break;
-			case "d|Z":
-				zCurve = curve;
-				break;
-			}
-		}
-	}
-	
-	@Override
-	public void link(FbxObject otherObject) {
-		layerId = otherObject.id;
-	}
-	
-	public boolean haveAnyChannel() {
-		return xCurve != null || yCurve != null || zCurve != null;
-	}
-	
-	public void exportTimes(Collection<Long> stamps) {
-		if(xCurve != null)
-			for(long t : xCurve.keyTimes)
-				stamps.add(t);
-		if(yCurve != null)
-			for(long t : yCurve.keyTimes)
-				stamps.add(t);
-		if(zCurve != null)
-			for(long t : zCurve.keyTimes)
-				stamps.add(t);
-	}
-	
-	public Vector3f getValue(long time, Vector3f defaultValue) {
-		float xValue = (xCurve != null) ? xCurve.getValue(time) : defaultValue.x;
-		float yValue = (yCurve != null) ? yCurve.getValue(time) : defaultValue.y;
-		float zValue = (zCurve != null) ? zCurve.getValue(time) : defaultValue.z;
-		return new Vector3f(xValue, yValue, zValue);
-	}
+
+    public Vector3f value;
+    public FbxAnimCurve xCurve;
+    public FbxAnimCurve yCurve;
+    public FbxAnimCurve zCurve;
+    public long layerId;
+
+    public FbxAnimNode(SceneLoader scene, FbxElement element) {
+        super(scene, element);
+        if(type.equals("")) {
+            Double x = null, y = null, z = null;
+            for(FbxElement e2 : element.getFbxProperties()) {
+                String propName = (String) e2.properties.get(0);
+                switch(propName) {
+                case "d|X":
+                    x = (Double) e2.properties.get(4);
+                    break;
+                case "d|Y":
+                    y = (Double) e2.properties.get(4);
+                    break;
+                case "d|Z":
+                    z = (Double) e2.properties.get(4);
+                    break;
+                }
+            }
+            // Load only T R S curve nodes
+            if(x != null && y != null && z != null)
+                value = new Vector3f(x.floatValue(), y.floatValue(), z.floatValue());
+        }
+    }
+
+    @Override
+    public void link(FbxObject otherObject, String propertyName) {
+        if(otherObject instanceof FbxAnimCurve) {
+            FbxAnimCurve curve = (FbxAnimCurve) otherObject;
+            switch(propertyName) {
+            case "d|X":
+                xCurve = curve;
+                break;
+            case "d|Y":
+                yCurve = curve;
+                break;
+            case "d|Z":
+                zCurve = curve;
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void link(FbxObject otherObject) {
+        layerId = otherObject.id;
+    }
+
+    public boolean haveAnyChannel() {
+        return xCurve != null || yCurve != null || zCurve != null;
+    }
+
+    public void exportTimes(Collection<Long> stamps) {
+        if(xCurve != null)
+            for(long t : xCurve.keyTimes)
+                stamps.add(t);
+        if(yCurve != null)
+            for(long t : yCurve.keyTimes)
+                stamps.add(t);
+        if(zCurve != null)
+            for(long t : zCurve.keyTimes)
+                stamps.add(t);
+    }
+
+    public Vector3f getValue(long time, Vector3f defaultValue) {
+        float xValue = (xCurve != null) ? xCurve.getValue(time) : defaultValue.x;
+        float yValue = (yCurve != null) ? yCurve.getValue(time) : defaultValue.y;
+        float zValue = (zCurve != null) ? zCurve.getValue(time) : defaultValue.z;
+        return new Vector3f(xValue, yValue, zValue);
+    }
 }

+ 43 - 43
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java

@@ -9,47 +9,47 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxBindPose extends FbxObject {
-	
-	public Map<Long, Matrix4f> nodeTransforms = new HashMap<>();
-	
-	public FbxBindPose(SceneLoader scene, FbxElement element) {
-		super(scene, element);
-		if(type.equals("BindPose")) {
-			for(FbxElement e : element.children) {
-				if(e.id.equals("PoseNode")) {
-					long nodeId = 0;
-					double[] transform = null;
-					for(FbxElement e2 : e.children) {
-						switch(e2.id) {
-						case "Node":
-							nodeId = (Long) e2.properties.get(0);
-							break;
-						case "Matrix":
-							transform = (double[]) e2.properties.get(0);
-							break;
-						}
-					}
-					Matrix4f t = buildTransform(transform);
-					t.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize));
-					nodeTransforms.put(nodeId, t);
-				}
-			}
-		}
-	}
-	
-	public void fillBindTransforms() {
-		for(long nodeId : nodeTransforms.keySet()) {
-			FbxNode node = scene.modelMap.get(nodeId);
-			node.bindTransform = nodeTransforms.get(nodeId).clone();
-		}
-	}
-	
-	private static Matrix4f buildTransform(double[] transform) {
-		float[] m = new float[transform.length];
-		for(int i = 0; i < transform.length; ++i)
-			m[i] = (float) transform[i];
-		Matrix4f matrix = new Matrix4f();
-		matrix.set(m, false);
-		return matrix;
-	}
+
+    public Map<Long, Matrix4f> nodeTransforms = new HashMap<>();
+
+    public FbxBindPose(SceneLoader scene, FbxElement element) {
+        super(scene, element);
+        if(type.equals("BindPose")) {
+            for(FbxElement e : element.children) {
+                if(e.id.equals("PoseNode")) {
+                    long nodeId = 0;
+                    double[] transform = null;
+                    for(FbxElement e2 : e.children) {
+                        switch(e2.id) {
+                        case "Node":
+                            nodeId = (Long) e2.properties.get(0);
+                            break;
+                        case "Matrix":
+                            transform = (double[]) e2.properties.get(0);
+                            break;
+                        }
+                    }
+                    Matrix4f t = buildTransform(transform);
+                    t.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize));
+                    nodeTransforms.put(nodeId, t);
+                }
+            }
+        }
+    }
+
+    public void fillBindTransforms() {
+        for(long nodeId : nodeTransforms.keySet()) {
+            FbxNode node = scene.modelMap.get(nodeId);
+            node.bindTransform = nodeTransforms.get(nodeId).clone();
+        }
+    }
+
+    private static Matrix4f buildTransform(double[] transform) {
+        float[] m = new float[transform.length];
+        for(int i = 0; i < transform.length; ++i)
+            m[i] = (float) transform[i];
+        Matrix4f matrix = new Matrix4f();
+        matrix.set(m, false);
+        return matrix;
+    }
 }

+ 35 - 35
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java

@@ -4,39 +4,39 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxCluster extends FbxObject {
-	
-	public int[] indexes;
-	public double[] weights;
-	public double[] transform;
-	public double[] transformLink;
-	public FbxSkin skin;
-	
-	public FbxCluster(SceneLoader scene, FbxElement element) {
-		super(scene, element);
-		for(FbxElement e : element.children) {
-			switch(e.id) {
-			case "Indexes":
-				indexes = (int[]) e.properties.get(0);
-				break;
-			case "Weights":
-				weights = (double[]) e.properties.get(0);
-				break;
-			case "Transform":
-				transform = (double[]) e.properties.get(0);
-				break;
-			case "TransformLink":
-				transformLink = (double[]) e.properties.get(0);
-				break;
-			}
-		}
-	}
-	
-	@Override
-	public void link(FbxObject child) {
-		if(child instanceof FbxNode) {
-			FbxNode limb = (FbxNode) child;
-			limb.skinToCluster.put(skin.id, this);
-			skin.bones.add(limb);
-		}
-	}
+
+    public int[] indexes;
+    public double[] weights;
+    public double[] transform;
+    public double[] transformLink;
+    public FbxSkin skin;
+
+    public FbxCluster(SceneLoader scene, FbxElement element) {
+        super(scene, element);
+        for(FbxElement e : element.children) {
+            switch(e.id) {
+            case "Indexes":
+                indexes = (int[]) e.properties.get(0);
+                break;
+            case "Weights":
+                weights = (double[]) e.properties.get(0);
+                break;
+            case "Transform":
+                transform = (double[]) e.properties.get(0);
+                break;
+            case "TransformLink":
+                transformLink = (double[]) e.properties.get(0);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void link(FbxObject child) {
+        if(child instanceof FbxNode) {
+            FbxNode limb = (FbxNode) child;
+            limb.skinToCluster.put(skin.id, this);
+            skin.bones.add(limb);
+        }
+    }
 }

+ 107 - 107
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java

@@ -13,111 +13,111 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxImage extends FbxObject {
-	
-	String filename;
-	String relativeFilename;
-	byte[] content;
-	String imageType;
-	
-	public Image image;
-	
-	public FbxImage(SceneLoader scene, FbxElement element) {
-		super(scene, element);
-		if(type.equals("Clip")) {
-			for(FbxElement e : element.children) {
-				switch(e.id) {
-				case "Type":
-					imageType = (String) e.properties.get(0);
-					break;
-				case "Filename":
-				case "FileName":
-					filename = (String) e.properties.get(0);
-					break;
-				case "RelativeFilename":
-					relativeFilename = (String) e.properties.get(0);
-					break;
-				case "Content":
-					if(e.properties.size() > 0)
-						content = (byte[]) e.properties.get(0);
-					break;
-				}
-			}
-			image = createImage();
-		}
-	}
-	
-	
-	private Image createImage() {
-		AssetManager assetManager = scene.assetManager;
-		Image image = null;
-		if(filename != null) {
-			// Try load by absolute path
-			File file = new File(filename);
-			if(file.exists() && file.isFile()) {
-				File dir = new File(file.getParent());
-				String locatorPath = dir.getAbsolutePath();
-				Texture tex = null;
-				try {
-					assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
-					tex = assetManager.loadTexture(file.getName());
-				} catch(Exception e) {} finally {
-					assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
-				}
-				if(tex != null)
-					image = tex.getImage();
-			}
-		}
-		if(image == null && relativeFilename != null) {
-			// Try load by relative path
-			File dir = new File(scene.sceneFolderName);
-			String locatorPath = dir.getAbsolutePath();
-			Texture tex = null;
-			try {
-				assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
-				tex = assetManager.loadTexture(relativeFilename);
-			} catch(Exception e) {} finally {
-				assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
-			}
-			if(tex != null)
-				image = tex.getImage();
-		}
-		if(image == null && content != null) {
-			// Try load from content
-			String filename = null;
-			if(this.filename != null)
-				filename = new File(this.filename).getName();
-			if(filename != null && this.relativeFilename != null)
-				filename = this.relativeFilename;
-			// Filename is required to acquire asset loader by extension
-			if(filename != null) {
-				String locatorPath = scene.sceneFilename;
-				filename = scene.sceneFilename + File.separatorChar + filename; // Unique path
-				Texture tex = null;
-				try {
-					assetManager.registerLocator(locatorPath, ContentTextureLocator.class);
-					tex = assetManager.loadTexture(new ContentTextureKey(filename, content));
-				} catch(Exception e) {} finally {
-					assetManager.unregisterLocator(locatorPath, ContentTextureLocator.class);
-				}
-				if(tex != null)
-					image = tex.getImage();
-			}
-		}
-		if(image == null) {
-			// Try to load from files near
-			if(relativeFilename != null) {
-				String[] split = relativeFilename.split("[\\\\/]");
-				String filename = split[split.length - 1];
-				Texture tex = null;
-				try {
-					tex = assetManager.loadTexture(new ContentTextureKey(scene.currentAssetInfo.getKey().getFolder() + filename, content));
-				} catch(Exception e) {}
-				if(tex != null)
-					image = tex.getImage();
-			}
-		}
-		if(image == null)
-			return new Image(Image.Format.RGB8, 1, 1, BufferUtils.createByteBuffer((int) (Image.Format.RGB8.getBitsPerPixel() / 8L)), ColorSpace.Linear);
-		return image;
-	}
+
+    String filename;
+    String relativeFilename;
+    byte[] content;
+    String imageType;
+
+    public Image image;
+
+    public FbxImage(SceneLoader scene, FbxElement element) {
+        super(scene, element);
+        if(type.equals("Clip")) {
+            for(FbxElement e : element.children) {
+                switch(e.id) {
+                case "Type":
+                    imageType = (String) e.properties.get(0);
+                    break;
+                case "Filename":
+                case "FileName":
+                    filename = (String) e.properties.get(0);
+                    break;
+                case "RelativeFilename":
+                    relativeFilename = (String) e.properties.get(0);
+                    break;
+                case "Content":
+                    if(e.properties.size() > 0)
+                        content = (byte[]) e.properties.get(0);
+                    break;
+                }
+            }
+            image = createImage();
+        }
+    }
+
+
+    private Image createImage() {
+        AssetManager assetManager = scene.assetManager;
+        Image image = null;
+        if(filename != null) {
+            // Try load by absolute path
+            File file = new File(filename);
+            if(file.exists() && file.isFile()) {
+                File dir = new File(file.getParent());
+                String locatorPath = dir.getAbsolutePath();
+                Texture tex = null;
+                try {
+                    assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
+                    tex = assetManager.loadTexture(file.getName());
+                } catch(Exception e) {} finally {
+                    assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
+                }
+                if(tex != null)
+                    image = tex.getImage();
+            }
+        }
+        if(image == null && relativeFilename != null) {
+            // Try load by relative path
+            File dir = new File(scene.sceneFolderName);
+            String locatorPath = dir.getAbsolutePath();
+            Texture tex = null;
+            try {
+                assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
+                tex = assetManager.loadTexture(relativeFilename);
+            } catch(Exception e) {} finally {
+                assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class);
+            }
+            if(tex != null)
+                image = tex.getImage();
+        }
+        if(image == null && content != null) {
+            // Try load from content
+            String filename = null;
+            if(this.filename != null)
+                filename = new File(this.filename).getName();
+            if(filename != null && this.relativeFilename != null)
+                filename = this.relativeFilename;
+            // Filename is required to acquire asset loader by extension
+            if(filename != null) {
+                String locatorPath = scene.sceneFilename;
+                filename = scene.sceneFilename + File.separatorChar + filename; // Unique path
+                Texture tex = null;
+                try {
+                    assetManager.registerLocator(locatorPath, ContentTextureLocator.class);
+                    tex = assetManager.loadTexture(new ContentTextureKey(filename, content));
+                } catch(Exception e) {} finally {
+                    assetManager.unregisterLocator(locatorPath, ContentTextureLocator.class);
+                }
+                if(tex != null)
+                    image = tex.getImage();
+            }
+        }
+        if(image == null) {
+            // Try to load from files near
+            if(relativeFilename != null) {
+                String[] split = relativeFilename.split("[\\\\/]");
+                String filename = split[split.length - 1];
+                Texture tex = null;
+                try {
+                    tex = assetManager.loadTexture(new ContentTextureKey(scene.currentAssetInfo.getKey().getFolder() + filename, content));
+                } catch(Exception e) {}
+                if(tex != null)
+                    image = tex.getImage();
+            }
+        }
+        if(image == null)
+            return new Image(Image.Format.RGB8, 1, 1, BufferUtils.createByteBuffer((int) (Image.Format.RGB8.getBitsPerPixel() / 8L)), ColorSpace.Linear);
+        return image;
+    }
 }

+ 102 - 102
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java

@@ -8,106 +8,106 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxMaterial extends FbxObject {
-	
-	public String shadingModel = "phong";
-	public Vector3f ambientColor = new Vector3f(0.2f, 0.2f, 0.2f);
-	public float ambientFactor = 1.0f;
-	public Vector3f diffuseColor = new Vector3f(0.8f, 0.8f, 0.8f);
-	public float diffuseFactor = 1.0f;
-	public Vector3f specularColor = new Vector3f(0.2f, 0.2f, 0.2f);
-	public float specularFactor = 1.0f;
-	public float shininessExponent = 1.0f;
-	
-	public Material material;
-	
-	public FbxMaterial(SceneLoader scene, FbxElement element) {
-		super(scene, element);
-		if(type.equals("")) {
-			for(FbxElement e : element.children) {
-				if(e.id.equals("ShadingModel")) {
-					shadingModel = (String) e.properties.get(0);
-				} else if(e.id.equals("Properties70")) {
-					for(FbxElement e2 : e.children) {
-						if(e2.id.equals("P")) {
-							double x, y, z;
-							String propName = (String) e2.properties.get(0);
-							switch(propName) {
-							case "AmbientColor":
-								x = (Double) e2.properties.get(4);
-								y = (Double) e2.properties.get(5);
-								z = (Double) e2.properties.get(6);
-								ambientColor.set((float) x, (float) y, (float) z);
-								break;
-							case "AmbientFactor":
-								x = (Double) e2.properties.get(4);
-								ambientFactor = (float) x;
-								break;
-							case "DiffuseColor":
-								x = (Double) e2.properties.get(4);
-								y = (Double) e2.properties.get(5);
-								z = (Double) e2.properties.get(6);
-								diffuseColor.set((float) x, (float) y, (float) z);
-								break;
-							case "DiffuseFactor":
-								x = (Double) e2.properties.get(4);
-								diffuseFactor = (float) x;
-								break;
-							case "SpecularColor":
-								x = (Double) e2.properties.get(4);
-								y = (Double) e2.properties.get(5);
-								z = (Double) e2.properties.get(6);
-								specularColor.set((float) x, (float) y, (float) z);
-								break;
-							case "Shininess":
-							case "ShininessExponent":
-								x = (Double) e2.properties.get(4);
-								shininessExponent = (float) x;
-								break;
-							}
-						}
-					}
-				}
-			}
-			material = createMaterial();
-		}
-	}
-	
-	@Override
-	public void link(FbxObject otherObject, String propertyName) {
-		if(otherObject instanceof FbxTexture) {
-			FbxTexture tex = (FbxTexture) otherObject;
-			if(tex.texture == null || material == null)
-				return;
-			switch(propertyName) {
-			case "DiffuseColor":
-				material.setTexture("DiffuseMap", tex.texture);
-				material.setColor("Diffuse", ColorRGBA.White);
-				break;
-			case "SpecularColor":
-				material.setTexture("SpecularMap", tex.texture);
-				material.setColor("Specular", ColorRGBA.White);
-				break;
-			case "NormalMap":
-				material.setTexture("NormalMap", tex.texture);
-				break;
-			}
-		}
-	}
-	
-	private Material createMaterial() {
-		Material m = new Material(scene.assetManager, "Common/MatDefs/Light/Lighting.j3md");
-		m.setName(name);
-		ambientColor.multLocal(ambientFactor);
-		diffuseColor.multLocal(diffuseFactor);
-		specularColor.multLocal(specularFactor);
-		m.setColor("Ambient", new ColorRGBA(ambientColor.x, ambientColor.y, ambientColor.z, 1));
-		m.setColor("Diffuse", new ColorRGBA(diffuseColor.x, diffuseColor.y, diffuseColor.z, 1));
-		m.setColor("Specular", new ColorRGBA(specularColor.x, specularColor.y, specularColor.z, 1));
-		m.setFloat("Shininess", shininessExponent);
-		m.setBoolean("UseMaterialColors", true);
-		m.setFloat("AlphaDiscardThreshold", 0.5f); // TODO replace with right way in JME to set "Alpha Test"
-		m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
-		return m;
-	}
-	
+
+    public String shadingModel = "phong";
+    public Vector3f ambientColor = new Vector3f(0.2f, 0.2f, 0.2f);
+    public float ambientFactor = 1.0f;
+    public Vector3f diffuseColor = new Vector3f(0.8f, 0.8f, 0.8f);
+    public float diffuseFactor = 1.0f;
+    public Vector3f specularColor = new Vector3f(0.2f, 0.2f, 0.2f);
+    public float specularFactor = 1.0f;
+    public float shininessExponent = 1.0f;
+
+    public Material material;
+
+    public FbxMaterial(SceneLoader scene, FbxElement element) {
+        super(scene, element);
+        if(type.equals("")) {
+            for(FbxElement e : element.children) {
+                if(e.id.equals("ShadingModel")) {
+                    shadingModel = (String) e.properties.get(0);
+                } else if(e.id.equals("Properties70")) {
+                    for(FbxElement e2 : e.children) {
+                        if(e2.id.equals("P")) {
+                            double x, y, z;
+                            String propName = (String) e2.properties.get(0);
+                            switch(propName) {
+                            case "AmbientColor":
+                                x = (Double) e2.properties.get(4);
+                                y = (Double) e2.properties.get(5);
+                                z = (Double) e2.properties.get(6);
+                                ambientColor.set((float) x, (float) y, (float) z);
+                                break;
+                            case "AmbientFactor":
+                                x = (Double) e2.properties.get(4);
+                                ambientFactor = (float) x;
+                                break;
+                            case "DiffuseColor":
+                                x = (Double) e2.properties.get(4);
+                                y = (Double) e2.properties.get(5);
+                                z = (Double) e2.properties.get(6);
+                                diffuseColor.set((float) x, (float) y, (float) z);
+                                break;
+                            case "DiffuseFactor":
+                                x = (Double) e2.properties.get(4);
+                                diffuseFactor = (float) x;
+                                break;
+                            case "SpecularColor":
+                                x = (Double) e2.properties.get(4);
+                                y = (Double) e2.properties.get(5);
+                                z = (Double) e2.properties.get(6);
+                                specularColor.set((float) x, (float) y, (float) z);
+                                break;
+                            case "Shininess":
+                            case "ShininessExponent":
+                                x = (Double) e2.properties.get(4);
+                                shininessExponent = (float) x;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            material = createMaterial();
+        }
+    }
+
+    @Override
+    public void link(FbxObject otherObject, String propertyName) {
+        if(otherObject instanceof FbxTexture) {
+            FbxTexture tex = (FbxTexture) otherObject;
+            if(tex.texture == null || material == null)
+                return;
+            switch(propertyName) {
+            case "DiffuseColor":
+                material.setTexture("DiffuseMap", tex.texture);
+                material.setColor("Diffuse", ColorRGBA.White);
+                break;
+            case "SpecularColor":
+                material.setTexture("SpecularMap", tex.texture);
+                material.setColor("Specular", ColorRGBA.White);
+                break;
+            case "NormalMap":
+                material.setTexture("NormalMap", tex.texture);
+                break;
+            }
+        }
+    }
+
+    private Material createMaterial() {
+        Material m = new Material(scene.assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        m.setName(name);
+        ambientColor.multLocal(ambientFactor);
+        diffuseColor.multLocal(diffuseFactor);
+        specularColor.multLocal(specularFactor);
+        m.setColor("Ambient", new ColorRGBA(ambientColor.x, ambientColor.y, ambientColor.z, 1));
+        m.setColor("Diffuse", new ColorRGBA(diffuseColor.x, diffuseColor.y, diffuseColor.z, 1));
+        m.setColor("Specular", new ColorRGBA(specularColor.x, specularColor.y, specularColor.z, 1));
+        m.setFloat("Shininess", shininessExponent);
+        m.setBoolean("UseMaterialColors", true);
+        m.setFloat("AlphaDiscardThreshold", 0.5f); // TODO replace with right way in JME to set "Alpha Test"
+        m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+        return m;
+    }
+
 }

+ 515 - 515
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java

@@ -19,519 +19,519 @@ import com.jme3.util.IntMap;
 import com.jme3.util.IntMap.Entry;
 
 public class FbxMesh extends FbxObject {
-	
-	public double[] vertices;
-	public int[] indices;
-	public int[] edges;
-	public String normalsMapping;
-	public String normalsReference;
-	public double[] normals;
-	public String tangentsMapping;
-	public String tangentsReference;
-	public double[] tangents;
-	public String binormalsMapping;
-	public String binormalsReference;
-	public double[] binormals;
-	public String uvMapping;
-	public String uvReference;
-	public double[] uv;
-	public int[] uvIndex;
-	public List<int[]> uvIndexes = new ArrayList<>();
-	public List<double[]> uvs = new ArrayList<>();
-	public String smoothingMapping;
-	public String smoothingReference;
-	public int[] smoothing;
-	public String materialsMapping;
-	public String materialsReference;
-	public int[] materials;
-	// Build helping data
-	public int iCount;
-	public int vCount;
-	public int srcVertexCount;
-	public List<Integer> vertexMap; // Target vertex -> source vertex
-	public List<List<Integer>> reverseVertexMap; // source vertex -> list of target vertices
-	public List<Integer> indexMap; // Target vertex -> source index
-	
-	public List<Geometry> geometries; // One mesh can be split in two geometries in case of by-polygon material mapping
-	public FbxNode parent;
-	public int lastMaterialId = 0;
-	
-	public FbxMesh(SceneLoader scene, FbxElement element) throws IOException {
-		super(scene, element);
-		if(type.equals("Mesh")) {
-			data: for(FbxElement e : element.children) {
-				switch(e.id) {
-				case "Vertices":
-					vertices = (double[]) e.properties.get(0);
-					break;
-				case "PolygonVertexIndex":
-					indices = (int[]) e.properties.get(0);
-					break;
-				// TODO edges are not used now
-				/*case "Edges":
-					edges = (int[]) e.properties.get(0);
-					break;*/
-				case "LayerElementNormal":
-					for(FbxElement e2 : e.children) {
-						switch(e2.id) {
-						case "MappingInformationType":
-							normalsMapping = (String) e2.properties.get(0);
-							if(!normalsMapping.equals("ByVertice") && !normalsMapping.equals("ByPolygonVertex")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementNormal.MappingInformationType attribute (" + normalsReference + ")");
-								continue data;
-							}
-							break;
-						case "ReferenceInformationType":
-							normalsReference = (String) e2.properties.get(0);
-							if(!normalsReference.equals("Direct")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementNormal.ReferenceInformationType attribute (" + normalsReference + ")");
-								continue data;
-							}
-							break;
-						case "Normals":
-							normals = (double[]) e2.properties.get(0);
-							break;
-						}
-					}
-					break;
-				case "LayerElementTangent":
-					for(FbxElement e2 : e.children) {
-						switch(e2.id) {
-						case "MappingInformationType":
-							tangentsMapping = (String) e2.properties.get(0);
-							if(!tangentsMapping.equals("ByVertice") && !tangentsMapping.equals("ByPolygonVertex")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementTangent.MappingInformationType attribute (" + tangentsMapping + ")");
-								continue data;
-							}
-							break;
-						case "ReferenceInformationType":
-							tangentsReference = (String) e2.properties.get(0);
-							if(!tangentsReference.equals("Direct")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementTangent.ReferenceInformationType attribute (" + tangentsReference + ")");
-								continue data;
-							}
-							break;
-						case "Tangents":
-							tangents = (double[]) e2.properties.get(0);
-							break;
-						}
-					}
-					break;
-				case "LayerElementBinormal":
-					for(FbxElement e2 : e.children) {
-						switch(e2.id) {
-						case "MappingInformationType":
-							binormalsMapping = (String) e2.properties.get(0);
-							if(!binormalsMapping.equals("ByVertice") && !binormalsMapping.equals("ByPolygonVertex")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementBinormal.MappingInformationType attribute (" + binormalsMapping + ")");
-								continue data;
-							}
-							break;
-						case "ReferenceInformationType":
-							binormalsReference = (String) e2.properties.get(0);
-							if(!binormalsReference.equals("Direct")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementBinormal.ReferenceInformationType attribute (" + binormalsReference + ")");
-								continue data;
-							}
-							break;
-						case "Tangents":
-							binormals = (double[]) e2.properties.get(0);
-							break;
-						}
-					}
-					break;
-				case "LayerElementUV":
-					for(FbxElement e2 : e.children) {
-						switch(e2.id) {
-						case "MappingInformationType":
-							uvMapping = (String) e2.properties.get(0);
-							if(!uvMapping.equals("ByPolygonVertex")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementUV.MappingInformationType attribute (" + uvMapping + ")");
-								continue data;
-							}
-							break;
-						case "ReferenceInformationType":
-							uvReference = (String) e2.properties.get(0);
-							if(!uvReference.equals("IndexToDirect")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementUV.ReferenceInformationType attribute (" + uvReference + ")");
-								continue data;
-							}
-							break;
-						case "UV":
-							uv = (double[]) e2.properties.get(0);
-							uvs.add(uv);
-							break;
-						case "UVIndex":
-							uvIndex = (int[]) e2.properties.get(0);
-							uvIndexes.add(uvIndex);
-							break;
-						}
-					}
-					break;
-				// TODO smoothing is not used now
-				/*case "LayerElementSmoothing":
-					for(FBXElement e2 : e.children) {
-						switch(e2.id) {
-						case "MappingInformationType":
-							smoothingMapping = (String) e2.properties.get(0);
-							if(!smoothingMapping.equals("ByEdge"))
-								throw new AssetLoadException("Not supported LayerElementSmoothing.MappingInformationType = " + smoothingMapping);
-							break;
-						case "ReferenceInformationType":
-							smoothingReference = (String) e2.properties.get(0);
-							if(!smoothingReference.equals("Direct"))
-								throw new AssetLoadException("Not supported LayerElementSmoothing.ReferenceInformationType = " + smoothingReference);
-							break;
-						case "Smoothing":
-							smoothing = (int[]) e2.properties.get(0);
-							break;
-						}
-					}
-					break;*/
-				case "LayerElementMaterial":
-					for(FbxElement e2 : e.children) {
-						switch(e2.id) {
-						case "MappingInformationType":
-							materialsMapping = (String) e2.properties.get(0);
-							if(!materialsMapping.equals("AllSame") && !materialsMapping.equals("ByPolygon")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementMaterial.MappingInformationType attribute (" + materialsMapping + ")");
-								continue data;
-							}
-							break;
-						case "ReferenceInformationType":
-							materialsReference = (String) e2.properties.get(0);
-							if(!materialsReference.equals("IndexToDirect")) {
-								if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
-									scene.warning("Ignored LayerElementMaterial.ReferenceInformationType attribute (" + materialsReference + ")");
-								continue data;
-							}
-							break;
-						case "Materials":
-							materials = (int[]) e2.properties.get(0);
-							break;
-						}
-					}
-					break;
-				}
-			}
-			geometries = createGeometries();
-		}
-	}
-	
-	public void setParent(Node node) {
-		for(int i = 0; i < geometries.size(); ++i) {
-			Geometry geom = geometries.get(i);
-			geom.setName(node.getName() + (i > 0 ? "-" + i : ""));
-			geom.updateModelBound();
-			node.attachChild(geom);
-		}
-	}
-	
-	@Override
-	public void linkToZero() {
-		setParent(scene.sceneNode);
-	}
-	
-	public void clearMaterials() {
-		for(Geometry g : geometries) {
-			if(g.getUserData("FBXMaterial") != null)
-				g.setUserData("FBXMaterial", null);
-		}
-	}
-	
-	@Override
-	public void link(FbxObject otherObject) {
-		if(otherObject instanceof FbxSkin) {
-			FbxSkin skin = (FbxSkin) otherObject;
-			skin.toSkin.add(this);
-		}
-	}
-	
-	private List<Geometry> createGeometries() throws IOException {
-		Mesh mesh = new Mesh();
-		mesh.setMode(Mode.Triangles);
-		// Since each vertex should contain unique texcoord and normal we should unroll vertex indexing
-		// So we don't use VertexBuffer.Type.Index for elements drawing
-		// Moreover quads should be triangulated (this increases number of vertices)
-		if(indices != null) {
-			iCount = indices.length;
-			srcVertexCount = vertices.length / 3;
-			// Indices contains negative numbers to define polygon last index
-			// Check indices strides to be sure we have triangles or quads
-			vCount = 0;
-			// Count number of vertices to be produced
-			int polyVertCount = 0;
-			for(int i = 0; i < iCount; ++i) {
-				int index = indices[i];
-				polyVertCount++;
-				if(index < 0) {
-					if(polyVertCount == 3) {
-						vCount += 3; // A triangle
-					} else if(polyVertCount == 4) {
-						vCount += 6; // A quad produce two triangles
-					} else {
-						throw new AssetLoadException("Unsupported PolygonVertexIndex stride");
-					}
-					polyVertCount = 0;
-				}
-			}
-			// Unroll index array into vertex mapping
-			vertexMap = new ArrayList<>(vCount);
-			indexMap = new ArrayList<>(vCount);
-			polyVertCount = 0;
-			for(int i = 0; i < iCount; ++i) {
-				int index = indices[i];
-				polyVertCount++;
-				if(index < 0) {
-					int lastIndex = -(index + 1);
-					if(polyVertCount == 3) {
-						vertexMap.add(indices[i - 2]);
-						vertexMap.add(indices[i - 1]);
-						vertexMap.add(lastIndex);
-						indexMap.add(i - 2);
-						indexMap.add(i - 1);
-						indexMap.add(i - 0);
-					} else if(polyVertCount == 4) {
-						vertexMap.add(indices[i - 3]);
-						vertexMap.add(indices[i - 2]);
-						vertexMap.add(indices[i - 1]);
-						vertexMap.add(indices[i - 3]);
-						vertexMap.add(indices[i - 1]);
-						vertexMap.add(lastIndex);
-						indexMap.add(i - 3);
-						indexMap.add(i - 2);
-						indexMap.add(i - 1);
-						indexMap.add(i - 3);
-						indexMap.add(i - 1);
-						indexMap.add(i - 0);
-					}
-					polyVertCount = 0;
-				}
-			}
-			// Build reverse vertex mapping
-			reverseVertexMap = new ArrayList<>(srcVertexCount);
-			for(int i = 0; i < srcVertexCount; ++i)
-				reverseVertexMap.add(new ArrayList<Integer>());
-			for(int i = 0; i < vCount; ++i) {
-				int index = vertexMap.get(i);
-				reverseVertexMap.get(index).add(i);
-			}
-		} else {
-			// Stub for no vertex indexing (direct mapping)
-			iCount = vCount = srcVertexCount;
-			vertexMap = new ArrayList<>(vCount);
-			indexMap = new ArrayList<>(vCount);
-			reverseVertexMap = new ArrayList<>(vCount);
-			for(int i = 0; i < vCount; ++i) {
-				vertexMap.set(i, i);
-				indexMap.set(i, i);
-				List<Integer> l = new ArrayList<>(1);
-				l.add(i);
-				reverseVertexMap.add(l);
-			}
-		}
-		if(vertices != null) {
-			// Unroll vertices data array
-			FloatBuffer positionBuffer = BufferUtils.createFloatBuffer(vCount * 3);
-			mesh.setBuffer(VertexBuffer.Type.Position, 3, positionBuffer);
-			int srcCount = vertices.length / 3;
-			for(int i = 0; i < vCount; ++i) {
-				int index = vertexMap.get(i);
-				if(index > srcCount)
-					throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount);
-				float x = (float) vertices[3 * index + 0] / scene.unitSize * scene.xAxis; // XXX Why we should scale by unit size?
-				float y = (float) vertices[3 * index + 1] / scene.unitSize * scene.yAxis;
-				float z = (float) vertices[3 * index + 2] / scene.unitSize * scene.zAxis;
-				positionBuffer.put(x).put(y).put(z);
-			}
-		}
-		if(normals != null) {
-			// Unroll normals data array
-			FloatBuffer normalBuffer = BufferUtils.createFloatBuffer(vCount * 3);
-			mesh.setBuffer(VertexBuffer.Type.Normal, 3, normalBuffer);
-			List<Integer> mapping = null;
-			if(normalsMapping.equals("ByVertice"))
-				mapping = vertexMap;
-			else if(normalsMapping.equals("ByPolygonVertex"))
-				mapping = indexMap;
-			else
-				throw new IOException("Unknown normals mapping type: " + normalsMapping);
-			int srcCount = normals.length / 3;
-			for(int i = 0; i < vCount; ++i) {
-				int index = mapping.get(i);
-				if(index > srcCount)
-					throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount);
-				float x = (float) normals[3 * index + 0] * scene.xAxis;
-				float y = (float) normals[3 * index + 1] * scene.yAxis;
-				float z = (float) normals[3 * index + 2] * scene.zAxis;
-				normalBuffer.put(x).put(y).put(z);
-			}
-		}
-		if(tangents != null) {
-			// Unroll normals data array
-			FloatBuffer tangentBuffer = BufferUtils.createFloatBuffer(vCount * 4);
-			mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tangentBuffer);
-			List<Integer> mapping = null;
-			if(tangentsMapping.equals("ByVertice"))
-				mapping = vertexMap;
-			else if(tangentsMapping.equals("ByPolygonVertex"))
-				mapping = indexMap;
-			else
-				throw new IOException("Unknown tangents mapping type: " + tangentsMapping);
-			int srcCount = tangents.length / 3;
-			for(int i = 0; i < vCount; ++i) {
-				int index = mapping.get(i);
-				if(index > srcCount)
-					throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount);
-				float x = (float) tangents[3 * index + 0] * scene.xAxis;
-				float y = (float) tangents[3 * index + 1] * scene.yAxis;
-				float z = (float) tangents[3 * index + 2] * scene.zAxis;
-				tangentBuffer.put(x).put(y).put(z).put(-1.0f);
-			}
-		}
-		if(binormals != null) {
-			// Unroll normals data array
-			FloatBuffer binormalBuffer = BufferUtils.createFloatBuffer(vCount * 3);
-			mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormalBuffer);
-			List<Integer> mapping = null;
-			if(binormalsMapping.equals("ByVertice"))
-				mapping = vertexMap;
-			else if(binormalsMapping.equals("ByPolygonVertex"))
-				mapping = indexMap;
-			else
-				throw new IOException("Unknown binormals mapping type: " + binormalsMapping);
-			int srcCount = binormals.length / 3;
-			for(int i = 0; i < vCount; ++i) {
-				int index = mapping.get(i);
-				if(index > srcCount)
-					throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount);
-				float x = (float) binormals[3 * index + 0] * scene.xAxis;
-				float y = (float) binormals[3 * index + 1] * scene.yAxis;
-				float z = (float) binormals[3 * index + 2] * scene.zAxis;
-				binormalBuffer.put(x).put(y).put(z);
-			}
-		}
-		for(int uvLayer = 0; uvLayer < uvs.size(); ++uvLayer) {
-			double[] uv = uvs.get(uvLayer);
-			int[] uvIndex = uvIndexes.size() > uvLayer ? uvIndexes.get(uvLayer) : null;
-			List<Integer> unIndexMap = vertexMap;
-			if(uvIndex != null) {
-				int uvIndexSrcCount = uvIndex.length;
-				if(uvIndexSrcCount != iCount)
-					throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + iCount);
-				// Unroll UV index array
-				unIndexMap = new ArrayList<>(vCount);
-				int polyVertCount = 0;
-				for(int i = 0; i < iCount; ++i) {
-					int index = indices[i];
-					polyVertCount++;
-					if(index < 0) {
-						if(polyVertCount == 3) {
-							unIndexMap.add(uvIndex[i - 2]);
-							unIndexMap.add(uvIndex[i - 1]);
-							unIndexMap.add(uvIndex[i - 0]);
-						} else if(polyVertCount == 4) {
-							unIndexMap.add(uvIndex[i - 3]);
-							unIndexMap.add(uvIndex[i - 2]);
-							unIndexMap.add(uvIndex[i - 1]);
-							unIndexMap.add(uvIndex[i - 3]);
-							unIndexMap.add(uvIndex[i - 1]);
-							unIndexMap.add(uvIndex[i - 0]);
-						}
-						polyVertCount = 0;
-					}
-				}
-			}
-			// Unroll UV data array
-			FloatBuffer uvBuffer = BufferUtils.createFloatBuffer(vCount * 2);
-			VertexBuffer.Type type = VertexBuffer.Type.TexCoord;
-			switch(uvLayer) {
-			case 1:
-				type = VertexBuffer.Type.TexCoord2;
-				break;
-			case 2:
-				type = VertexBuffer.Type.TexCoord3;
-				break;
-			case 3:
-				type = VertexBuffer.Type.TexCoord4;
-				break;
-			case 4:
-				type = VertexBuffer.Type.TexCoord5;
-				break;
-			case 5:
-				type = VertexBuffer.Type.TexCoord6;
-				break;
-			case 6:
-				type = VertexBuffer.Type.TexCoord7;
-				break;
-			case 7:
-				type = VertexBuffer.Type.TexCoord8;
-				break;
-			}
-			mesh.setBuffer(type, 2, uvBuffer);
-			int srcCount = uv.length / 2;
-			for(int i = 0; i < vCount; ++i) {
-				int index = unIndexMap.get(i);
-				if(index > srcCount)
-					throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount);
-				float u = (index >= 0) ? (float) uv[2 * index + 0] : 0;
-				float v = (index >= 0) ? (float) uv[2 * index + 1] : 0;
-				uvBuffer.put(u).put(v);
-			}
-		}
-		List<Geometry> geometries = new ArrayList<>();
-		if(materialsReference.equals("IndexToDirect") && materialsMapping.equals("ByPolygon")) {
-			IntMap<List<Integer>> indexBuffers = new IntMap<>();
-			for(int polygon = 0; polygon < materials.length; ++polygon) {
-				int material = materials[polygon];
-				List<Integer> list = indexBuffers.get(material);
-				if(list == null) {
-					list = new ArrayList<>();
-					indexBuffers.put(material, list);
-				}
-				list.add(polygon * 3 + 0);
-				list.add(polygon * 3 + 1);
-				list.add(polygon * 3 + 2);
-			}
-			Iterator<Entry<List<Integer>>> iterator = indexBuffers.iterator();
-			while(iterator.hasNext()) {
-				Entry<List<Integer>> e = iterator.next();
-				int materialId = e.getKey();
-				List<Integer> indexes = e.getValue();
-				Mesh newMesh = mesh.clone();
-				newMesh.setBuffer(VertexBuffer.Type.Index, 3, toArray(indexes.toArray(new Integer[indexes.size()])));
-				newMesh.setStatic();
-				newMesh.updateBound();
-				newMesh.updateCounts();
-				Geometry geom = new Geometry();
-				geom.setMesh(newMesh);
-				geometries.add(geom);
-				geom.setUserData("FBXMaterial", materialId);
-			}
-		} else {
-			mesh.setStatic();
-			mesh.updateBound();
-			mesh.updateCounts();
-			Geometry geom = new Geometry();
-			geom.setMesh(mesh);
-			geometries.add(geom);
-		}
-		return geometries;
-	}
-	
-	private static int[] toArray(Integer[] arr) {
-		int[] ret = new int[arr.length];
-		for(int i = 0; i < arr.length; ++i)
-			ret[i] = arr[i].intValue();
-		return ret;
-	}
+
+    public double[] vertices;
+    public int[] indices;
+    public int[] edges;
+    public String normalsMapping;
+    public String normalsReference;
+    public double[] normals;
+    public String tangentsMapping;
+    public String tangentsReference;
+    public double[] tangents;
+    public String binormalsMapping;
+    public String binormalsReference;
+    public double[] binormals;
+    public String uvMapping;
+    public String uvReference;
+    public double[] uv;
+    public int[] uvIndex;
+    public List<int[]> uvIndexes = new ArrayList<>();
+    public List<double[]> uvs = new ArrayList<>();
+    public String smoothingMapping;
+    public String smoothingReference;
+    public int[] smoothing;
+    public String materialsMapping;
+    public String materialsReference;
+    public int[] materials;
+    // Build helping data
+    public int iCount;
+    public int vCount;
+    public int srcVertexCount;
+    public List<Integer> vertexMap; // Target vertex -> source vertex
+    public List<List<Integer>> reverseVertexMap; // source vertex -> list of target vertices
+    public List<Integer> indexMap; // Target vertex -> source index
+
+    public List<Geometry> geometries; // One mesh can be split in two geometries in case of by-polygon material mapping
+    public FbxNode parent;
+    public int lastMaterialId = 0;
+
+    public FbxMesh(SceneLoader scene, FbxElement element) throws IOException {
+        super(scene, element);
+        if(type.equals("Mesh")) {
+            data: for(FbxElement e : element.children) {
+                switch(e.id) {
+                case "Vertices":
+                    vertices = (double[]) e.properties.get(0);
+                    break;
+                case "PolygonVertexIndex":
+                    indices = (int[]) e.properties.get(0);
+                    break;
+                // TODO edges are not used now
+                /*case "Edges":
+                    edges = (int[]) e.properties.get(0);
+                    break;*/
+                case "LayerElementNormal":
+                    for(FbxElement e2 : e.children) {
+                        switch(e2.id) {
+                        case "MappingInformationType":
+                            normalsMapping = (String) e2.properties.get(0);
+                            if(!normalsMapping.equals("ByVertice") && !normalsMapping.equals("ByPolygonVertex")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementNormal.MappingInformationType attribute (" + normalsReference + ")");
+                                continue data;
+                            }
+                            break;
+                        case "ReferenceInformationType":
+                            normalsReference = (String) e2.properties.get(0);
+                            if(!normalsReference.equals("Direct")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementNormal.ReferenceInformationType attribute (" + normalsReference + ")");
+                                continue data;
+                            }
+                            break;
+                        case "Normals":
+                            normals = (double[]) e2.properties.get(0);
+                            break;
+                        }
+                    }
+                    break;
+                case "LayerElementTangent":
+                    for(FbxElement e2 : e.children) {
+                        switch(e2.id) {
+                        case "MappingInformationType":
+                            tangentsMapping = (String) e2.properties.get(0);
+                            if(!tangentsMapping.equals("ByVertice") && !tangentsMapping.equals("ByPolygonVertex")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementTangent.MappingInformationType attribute (" + tangentsMapping + ")");
+                                continue data;
+                            }
+                            break;
+                        case "ReferenceInformationType":
+                            tangentsReference = (String) e2.properties.get(0);
+                            if(!tangentsReference.equals("Direct")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementTangent.ReferenceInformationType attribute (" + tangentsReference + ")");
+                                continue data;
+                            }
+                            break;
+                        case "Tangents":
+                            tangents = (double[]) e2.properties.get(0);
+                            break;
+                        }
+                    }
+                    break;
+                case "LayerElementBinormal":
+                    for(FbxElement e2 : e.children) {
+                        switch(e2.id) {
+                        case "MappingInformationType":
+                            binormalsMapping = (String) e2.properties.get(0);
+                            if(!binormalsMapping.equals("ByVertice") && !binormalsMapping.equals("ByPolygonVertex")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementBinormal.MappingInformationType attribute (" + binormalsMapping + ")");
+                                continue data;
+                            }
+                            break;
+                        case "ReferenceInformationType":
+                            binormalsReference = (String) e2.properties.get(0);
+                            if(!binormalsReference.equals("Direct")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementBinormal.ReferenceInformationType attribute (" + binormalsReference + ")");
+                                continue data;
+                            }
+                            break;
+                        case "Tangents":
+                            binormals = (double[]) e2.properties.get(0);
+                            break;
+                        }
+                    }
+                    break;
+                case "LayerElementUV":
+                    for(FbxElement e2 : e.children) {
+                        switch(e2.id) {
+                        case "MappingInformationType":
+                            uvMapping = (String) e2.properties.get(0);
+                            if(!uvMapping.equals("ByPolygonVertex")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementUV.MappingInformationType attribute (" + uvMapping + ")");
+                                continue data;
+                            }
+                            break;
+                        case "ReferenceInformationType":
+                            uvReference = (String) e2.properties.get(0);
+                            if(!uvReference.equals("IndexToDirect")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementUV.ReferenceInformationType attribute (" + uvReference + ")");
+                                continue data;
+                            }
+                            break;
+                        case "UV":
+                            uv = (double[]) e2.properties.get(0);
+                            uvs.add(uv);
+                            break;
+                        case "UVIndex":
+                            uvIndex = (int[]) e2.properties.get(0);
+                            uvIndexes.add(uvIndex);
+                            break;
+                        }
+                    }
+                    break;
+                // TODO smoothing is not used now
+                /*case "LayerElementSmoothing":
+                    for(FBXElement e2 : e.children) {
+                        switch(e2.id) {
+                        case "MappingInformationType":
+                            smoothingMapping = (String) e2.properties.get(0);
+                            if(!smoothingMapping.equals("ByEdge"))
+                                throw new AssetLoadException("Not supported LayerElementSmoothing.MappingInformationType = " + smoothingMapping);
+                            break;
+                        case "ReferenceInformationType":
+                            smoothingReference = (String) e2.properties.get(0);
+                            if(!smoothingReference.equals("Direct"))
+                                throw new AssetLoadException("Not supported LayerElementSmoothing.ReferenceInformationType = " + smoothingReference);
+                            break;
+                        case "Smoothing":
+                            smoothing = (int[]) e2.properties.get(0);
+                            break;
+                        }
+                    }
+                    break;*/
+                case "LayerElementMaterial":
+                    for(FbxElement e2 : e.children) {
+                        switch(e2.id) {
+                        case "MappingInformationType":
+                            materialsMapping = (String) e2.properties.get(0);
+                            if(!materialsMapping.equals("AllSame") && !materialsMapping.equals("ByPolygon")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementMaterial.MappingInformationType attribute (" + materialsMapping + ")");
+                                continue data;
+                            }
+                            break;
+                        case "ReferenceInformationType":
+                            materialsReference = (String) e2.properties.get(0);
+                            if(!materialsReference.equals("IndexToDirect")) {
+                                if(SceneLoader.WARN_IGNORED_ATTRIBUTES)
+                                    scene.warning("Ignored LayerElementMaterial.ReferenceInformationType attribute (" + materialsReference + ")");
+                                continue data;
+                            }
+                            break;
+                        case "Materials":
+                            materials = (int[]) e2.properties.get(0);
+                            break;
+                        }
+                    }
+                    break;
+                }
+            }
+            geometries = createGeometries();
+        }
+    }
+
+    public void setParent(Node node) {
+        for(int i = 0; i < geometries.size(); ++i) {
+            Geometry geom = geometries.get(i);
+            geom.setName(node.getName() + (i > 0 ? "-" + i : ""));
+            geom.updateModelBound();
+            node.attachChild(geom);
+        }
+    }
+
+    @Override
+    public void linkToZero() {
+        setParent(scene.sceneNode);
+    }
+
+    public void clearMaterials() {
+        for(Geometry g : geometries) {
+            if(g.getUserData("FBXMaterial") != null)
+                g.setUserData("FBXMaterial", null);
+        }
+    }
+
+    @Override
+    public void link(FbxObject otherObject) {
+        if(otherObject instanceof FbxSkin) {
+            FbxSkin skin = (FbxSkin) otherObject;
+            skin.toSkin.add(this);
+        }
+    }
+
+    private List<Geometry> createGeometries() throws IOException {
+        Mesh mesh = new Mesh();
+        mesh.setMode(Mode.Triangles);
+        // Since each vertex should contain unique texcoord and normal we should unroll vertex indexing
+        // So we don't use VertexBuffer.Type.Index for elements drawing
+        // Moreover quads should be triangulated (this increases number of vertices)
+        if(indices != null) {
+            iCount = indices.length;
+            srcVertexCount = vertices.length / 3;
+            // Indices contains negative numbers to define polygon last index
+            // Check indices strides to be sure we have triangles or quads
+            vCount = 0;
+            // Count number of vertices to be produced
+            int polyVertCount = 0;
+            for(int i = 0; i < iCount; ++i) {
+                int index = indices[i];
+                polyVertCount++;
+                if(index < 0) {
+                    if(polyVertCount == 3) {
+                        vCount += 3; // A triangle
+                    } else if(polyVertCount == 4) {
+                        vCount += 6; // A quad produce two triangles
+                    } else {
+                        throw new AssetLoadException("Unsupported PolygonVertexIndex stride");
+                    }
+                    polyVertCount = 0;
+                }
+            }
+            // Unroll index array into vertex mapping
+            vertexMap = new ArrayList<>(vCount);
+            indexMap = new ArrayList<>(vCount);
+            polyVertCount = 0;
+            for(int i = 0; i < iCount; ++i) {
+                int index = indices[i];
+                polyVertCount++;
+                if(index < 0) {
+                    int lastIndex = -(index + 1);
+                    if(polyVertCount == 3) {
+                        vertexMap.add(indices[i - 2]);
+                        vertexMap.add(indices[i - 1]);
+                        vertexMap.add(lastIndex);
+                        indexMap.add(i - 2);
+                        indexMap.add(i - 1);
+                        indexMap.add(i - 0);
+                    } else if(polyVertCount == 4) {
+                        vertexMap.add(indices[i - 3]);
+                        vertexMap.add(indices[i - 2]);
+                        vertexMap.add(indices[i - 1]);
+                        vertexMap.add(indices[i - 3]);
+                        vertexMap.add(indices[i - 1]);
+                        vertexMap.add(lastIndex);
+                        indexMap.add(i - 3);
+                        indexMap.add(i - 2);
+                        indexMap.add(i - 1);
+                        indexMap.add(i - 3);
+                        indexMap.add(i - 1);
+                        indexMap.add(i - 0);
+                    }
+                    polyVertCount = 0;
+                }
+            }
+            // Build reverse vertex mapping
+            reverseVertexMap = new ArrayList<>(srcVertexCount);
+            for(int i = 0; i < srcVertexCount; ++i)
+                reverseVertexMap.add(new ArrayList<Integer>());
+            for(int i = 0; i < vCount; ++i) {
+                int index = vertexMap.get(i);
+                reverseVertexMap.get(index).add(i);
+            }
+        } else {
+            // Stub for no vertex indexing (direct mapping)
+            iCount = vCount = srcVertexCount;
+            vertexMap = new ArrayList<>(vCount);
+            indexMap = new ArrayList<>(vCount);
+            reverseVertexMap = new ArrayList<>(vCount);
+            for(int i = 0; i < vCount; ++i) {
+                vertexMap.set(i, i);
+                indexMap.set(i, i);
+                List<Integer> l = new ArrayList<>(1);
+                l.add(i);
+                reverseVertexMap.add(l);
+            }
+        }
+        if(vertices != null) {
+            // Unroll vertices data array
+            FloatBuffer positionBuffer = BufferUtils.createFloatBuffer(vCount * 3);
+            mesh.setBuffer(VertexBuffer.Type.Position, 3, positionBuffer);
+            int srcCount = vertices.length / 3;
+            for(int i = 0; i < vCount; ++i) {
+                int index = vertexMap.get(i);
+                if(index > srcCount)
+                    throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount);
+                float x = (float) vertices[3 * index + 0] / scene.unitSize * scene.xAxis; // XXX Why we should scale by unit size?
+                float y = (float) vertices[3 * index + 1] / scene.unitSize * scene.yAxis;
+                float z = (float) vertices[3 * index + 2] / scene.unitSize * scene.zAxis;
+                positionBuffer.put(x).put(y).put(z);
+            }
+        }
+        if(normals != null) {
+            // Unroll normals data array
+            FloatBuffer normalBuffer = BufferUtils.createFloatBuffer(vCount * 3);
+            mesh.setBuffer(VertexBuffer.Type.Normal, 3, normalBuffer);
+            List<Integer> mapping = null;
+            if(normalsMapping.equals("ByVertice"))
+                mapping = vertexMap;
+            else if(normalsMapping.equals("ByPolygonVertex"))
+                mapping = indexMap;
+            else
+                throw new IOException("Unknown normals mapping type: " + normalsMapping);
+            int srcCount = normals.length / 3;
+            for(int i = 0; i < vCount; ++i) {
+                int index = mapping.get(i);
+                if(index > srcCount)
+                    throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount);
+                float x = (float) normals[3 * index + 0] * scene.xAxis;
+                float y = (float) normals[3 * index + 1] * scene.yAxis;
+                float z = (float) normals[3 * index + 2] * scene.zAxis;
+                normalBuffer.put(x).put(y).put(z);
+            }
+        }
+        if(tangents != null) {
+            // Unroll normals data array
+            FloatBuffer tangentBuffer = BufferUtils.createFloatBuffer(vCount * 4);
+            mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tangentBuffer);
+            List<Integer> mapping = null;
+            if(tangentsMapping.equals("ByVertice"))
+                mapping = vertexMap;
+            else if(tangentsMapping.equals("ByPolygonVertex"))
+                mapping = indexMap;
+            else
+                throw new IOException("Unknown tangents mapping type: " + tangentsMapping);
+            int srcCount = tangents.length / 3;
+            for(int i = 0; i < vCount; ++i) {
+                int index = mapping.get(i);
+                if(index > srcCount)
+                    throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount);
+                float x = (float) tangents[3 * index + 0] * scene.xAxis;
+                float y = (float) tangents[3 * index + 1] * scene.yAxis;
+                float z = (float) tangents[3 * index + 2] * scene.zAxis;
+                tangentBuffer.put(x).put(y).put(z).put(-1.0f);
+            }
+        }
+        if(binormals != null) {
+            // Unroll normals data array
+            FloatBuffer binormalBuffer = BufferUtils.createFloatBuffer(vCount * 3);
+            mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormalBuffer);
+            List<Integer> mapping = null;
+            if(binormalsMapping.equals("ByVertice"))
+                mapping = vertexMap;
+            else if(binormalsMapping.equals("ByPolygonVertex"))
+                mapping = indexMap;
+            else
+                throw new IOException("Unknown binormals mapping type: " + binormalsMapping);
+            int srcCount = binormals.length / 3;
+            for(int i = 0; i < vCount; ++i) {
+                int index = mapping.get(i);
+                if(index > srcCount)
+                    throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount);
+                float x = (float) binormals[3 * index + 0] * scene.xAxis;
+                float y = (float) binormals[3 * index + 1] * scene.yAxis;
+                float z = (float) binormals[3 * index + 2] * scene.zAxis;
+                binormalBuffer.put(x).put(y).put(z);
+            }
+        }
+        for(int uvLayer = 0; uvLayer < uvs.size(); ++uvLayer) {
+            double[] uv = uvs.get(uvLayer);
+            int[] uvIndex = uvIndexes.size() > uvLayer ? uvIndexes.get(uvLayer) : null;
+            List<Integer> unIndexMap = vertexMap;
+            if(uvIndex != null) {
+                int uvIndexSrcCount = uvIndex.length;
+                if(uvIndexSrcCount != iCount)
+                    throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + iCount);
+                // Unroll UV index array
+                unIndexMap = new ArrayList<>(vCount);
+                int polyVertCount = 0;
+                for(int i = 0; i < iCount; ++i) {
+                    int index = indices[i];
+                    polyVertCount++;
+                    if(index < 0) {
+                        if(polyVertCount == 3) {
+                            unIndexMap.add(uvIndex[i - 2]);
+                            unIndexMap.add(uvIndex[i - 1]);
+                            unIndexMap.add(uvIndex[i - 0]);
+                        } else if(polyVertCount == 4) {
+                            unIndexMap.add(uvIndex[i - 3]);
+                            unIndexMap.add(uvIndex[i - 2]);
+                            unIndexMap.add(uvIndex[i - 1]);
+                            unIndexMap.add(uvIndex[i - 3]);
+                            unIndexMap.add(uvIndex[i - 1]);
+                            unIndexMap.add(uvIndex[i - 0]);
+                        }
+                        polyVertCount = 0;
+                    }
+                }
+            }
+            // Unroll UV data array
+            FloatBuffer uvBuffer = BufferUtils.createFloatBuffer(vCount * 2);
+            VertexBuffer.Type type = VertexBuffer.Type.TexCoord;
+            switch(uvLayer) {
+            case 1:
+                type = VertexBuffer.Type.TexCoord2;
+                break;
+            case 2:
+                type = VertexBuffer.Type.TexCoord3;
+                break;
+            case 3:
+                type = VertexBuffer.Type.TexCoord4;
+                break;
+            case 4:
+                type = VertexBuffer.Type.TexCoord5;
+                break;
+            case 5:
+                type = VertexBuffer.Type.TexCoord6;
+                break;
+            case 6:
+                type = VertexBuffer.Type.TexCoord7;
+                break;
+            case 7:
+                type = VertexBuffer.Type.TexCoord8;
+                break;
+            }
+            mesh.setBuffer(type, 2, uvBuffer);
+            int srcCount = uv.length / 2;
+            for(int i = 0; i < vCount; ++i) {
+                int index = unIndexMap.get(i);
+                if(index > srcCount)
+                    throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount);
+                float u = (index >= 0) ? (float) uv[2 * index + 0] : 0;
+                float v = (index >= 0) ? (float) uv[2 * index + 1] : 0;
+                uvBuffer.put(u).put(v);
+            }
+        }
+        List<Geometry> geometries = new ArrayList<>();
+        if(materialsReference.equals("IndexToDirect") && materialsMapping.equals("ByPolygon")) {
+            IntMap<List<Integer>> indexBuffers = new IntMap<>();
+            for(int polygon = 0; polygon < materials.length; ++polygon) {
+                int material = materials[polygon];
+                List<Integer> list = indexBuffers.get(material);
+                if(list == null) {
+                    list = new ArrayList<>();
+                    indexBuffers.put(material, list);
+                }
+                list.add(polygon * 3 + 0);
+                list.add(polygon * 3 + 1);
+                list.add(polygon * 3 + 2);
+            }
+            Iterator<Entry<List<Integer>>> iterator = indexBuffers.iterator();
+            while(iterator.hasNext()) {
+                Entry<List<Integer>> e = iterator.next();
+                int materialId = e.getKey();
+                List<Integer> indexes = e.getValue();
+                Mesh newMesh = mesh.clone();
+                newMesh.setBuffer(VertexBuffer.Type.Index, 3, toArray(indexes.toArray(new Integer[indexes.size()])));
+                newMesh.setStatic();
+                newMesh.updateBound();
+                newMesh.updateCounts();
+                Geometry geom = new Geometry();
+                geom.setMesh(newMesh);
+                geometries.add(geom);
+                geom.setUserData("FBXMaterial", materialId);
+            }
+        } else {
+            mesh.setStatic();
+            mesh.updateBound();
+            mesh.updateCounts();
+            Geometry geom = new Geometry();
+            geom.setMesh(mesh);
+            geometries.add(geom);
+        }
+        return geometries;
+    }
+
+    private static int[] toArray(Integer[] arr) {
+        int[] ret = new int[arr.length];
+        for(int i = 0; i < arr.length; ++i)
+            ret[i] = arr[i].intValue();
+        return ret;
+    }
 }

+ 251 - 251
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java

@@ -18,255 +18,255 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxNode extends FbxObject {
-	
-	public FaceCullMode cullMode = FaceCullMode.Back;
-	public Transform localTransform;
-	public Node node;
-	public FbxNode parentFbxNode;
-	
-	public boolean rotationActive = false;
-	public RotationOrder rotationOrder = RotationOrder.EULER_XYZ;
-	
-	
-	// For bones and animation, in world space
-	public Matrix4f bindTransform = null;
-	public int boneIndex;
-	public Map<Long, FbxAnimNode> animTranslations = new HashMap<>();
-	public Map<Long, FbxAnimNode> animRotations = new HashMap<>();
-	public Map<Long, FbxAnimNode> animScales = new HashMap<>();
-	public Bone bone;
-	private FbxAnimNode lastAnimTranslation;
-	private FbxAnimNode lastAnimRotation;
-	private FbxAnimNode lastAnimScale;
-	private FbxMesh mesh;
-	public Map<Long, FbxCluster> skinToCluster = new HashMap<>();
-	
-	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);
-			switch(propName) {
-			case "RotationOrder":
-				rotationOrder = RotationOrder.values[(Integer) prop.properties.get(4)];
-				break;
-			case "Lcl Translation":
-				readVectorFromProp(translationLocalRaw, prop);
-				break;
-			case "Lcl Rotation":
-				readVectorFromProp(rotationLocalRaw, prop);
-				break;
-			case "Lcl Scaling":
-				readVectorFromProp(scaleLocalRaw, prop);
-				break;
-			case "PreRotation":
-				readVectorFromProp(rotationPreRaw, prop);
-				break;
-			case "RotationActive":
-				rotationActive = ((Number) prop.properties.get(4)).intValue() == 1;
-				break;
-			case "RotationPivot":
-				readVectorFromProp(rotationPivotRaw, prop);
-				break;
-			case "PostRotation":
-				readVectorFromProp(rotationPostRaw, prop);
-				break;
-			case "ScaleOffset":
-				readVectorFromProp(scaleOffsetRaw, prop);
-				break;
-			case "ScalePivot":
-				readVectorFromProp(scalePivotRaw, prop);
-				break;
-			case "U":
-				String userDataKey = (String) prop.properties.get(0);
-				String userDataType = (String) prop.properties.get(1);
-				Object userDataValue;
-				if(userDataType.equals("KString")) {
-					userDataValue = prop.properties.get(4);
-				} else if(userDataType.equals("int")) {
-					userDataValue = prop.properties.get(4);
-				} else if(userDataType.equals("double")) {
-					// NOTE: jME3 does not support doubles in UserData.
-					//       Need to convert to float.
-					userDataValue = ((Double) prop.properties.get(4)).floatValue();
-				} else if(userDataType.equals("Vector")) {
-					x = (Double) prop.properties.get(4);
-					y = (Double) prop.properties.get(5);
-					z = (Double) prop.properties.get(6);
-					userDataValue = new Vector3f((float) x, (float) y, (float) z);
-				} else {
-					scene.warning("Unsupported user data type: " + userDataType + ". Ignoring.");
-					continue;
-				}
-				node.setUserData(userDataKey, userDataValue);
-				break;
-			}
-		}
-		
-		FbxElement cullingElement = element.getChildById("Culling");
-		if(cullingElement != null && cullingElement.properties.get(0).equals("CullingOff"))
-			cullMode = FaceCullMode.Off; // TODO Add other variants
-		
-		/*From http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/the-makeup-of-the-local-matrix-of-an-kfbxnode/
-		
-		Local Matrix = LclTranslation * RotationOffset * RotationPivot *
-		  PreRotation * LclRotation * PostRotation * RotationPivotInverse *
-		  ScalingOffset * ScalingPivot * LclScaling * ScalingPivotInverse
-		
-		LocalTranslation : translate (xform -query -translation)
-		RotationOffset: translation compensates for the change in the rotate pivot point (xform -q -rotateTranslation)
-		RotationPivot: current rotate pivot position (xform -q -rotatePivot)
-		PreRotation : joint orientation(pre rotation)
-		LocalRotation: rotate transform (xform -q -rotation & xform -q -rotateOrder)
-		PostRotation : rotate axis (xform -q -rotateAxis)
-		RotationPivotInverse: inverse of RotationPivot
-		ScalingOffset: translation compensates for the change in the scale pivot point (xform -q -scaleTranslation)
-		ScalingPivot: current scale pivot position (xform -q -scalePivot)
-		LocalScaling: scale transform (xform -q -scale)
-		ScalingPivotInverse: inverse of ScalingPivot
-		*/
-		
-		RotationOrder rotOrder = rotationActive ? rotationOrder : RotationOrder.EULER_XYZ;
-		
-		Matrix4f transformMatrix = new Matrix4f();
-		transformMatrix.setTranslation(translationLocalRaw.x + rotationOffsetRaw.x + rotationPivotRaw.x, translationLocalRaw.y + rotationOffsetRaw.y + rotationPivotRaw.y, translationLocalRaw.z + rotationOffsetRaw.z + rotationPivotRaw.z);
-		
-		if(rotationActive) {
-			Quaternion postRotation = rotOrder.rotate(rotationPostRaw.x, rotationPostRaw.y, rotationPostRaw.z);
-			Quaternion localRotation = rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z);
-			Quaternion preRotation = rotOrder.rotate(rotationPreRaw.x, rotationPreRaw.y, rotationPreRaw.z);
-			//preRotation.multLocal(localRotation).multLocal(postRotation);
-			postRotation.multLocal(localRotation).multLocal(preRotation);
-			transformMatrix.multLocal(postRotation);
-		} else {
-			transformMatrix.multLocal(rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z));
-		}
-		
-		Matrix4f mat = new Matrix4f();
-		mat.setTranslation(scaleOffsetRaw.x + scalePivotRaw.x - rotationPivotRaw.x, scaleOffsetRaw.y + scalePivotRaw.y - rotationPivotRaw.y, scaleOffsetRaw.z + scalePivotRaw.z - rotationPivotRaw.z);
-		transformMatrix.multLocal(mat);
-		
-		transformMatrix.scale(scaleLocalRaw);
-		transformMatrix.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize));
-		
-		mat.setTranslation(scalePivotRaw.negate());
-		transformMatrix.multLocal(mat);
-		
-		localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector());
-		
-		node.setLocalTransform(localTransform);
-	}
-	
-	@Override
-	public void linkToZero() {
-		scene.sceneNode.attachChild(node);
-	}
-	
-	public void setSkeleton(Skeleton skeleton) {
-		if(bone != null)
-			boneIndex = skeleton.getBoneIndex(bone);
-	}
-	
-	public void buildBindPoseBoneTransform() {
-		if(bone != null) {
-			Matrix4f t = bindTransform;
-			if(t != null) {
-				Matrix4f parentMatrix = parentFbxNode != null ? parentFbxNode.bindTransform : Matrix4f.IDENTITY;
-				if(parentMatrix == null)
-					parentMatrix = node.getLocalToWorldMatrix(null);
-				t = parentMatrix.invert().multLocal(t);
-				bone.setBindTransforms(t.toTranslationVector(), t.toRotationQuat(), t.toScaleVector());
-			} else {
-				bone.setBindTransforms(node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale());
-			}
-		}
-	}
-	
-	@Override
-	public void link(FbxObject child, String propertyName) {
-		if(child instanceof FbxAnimNode) {
-			FbxAnimNode anim = (FbxAnimNode) child;
-			switch(propertyName) {
-			case "Lcl Translation":
-				animTranslations.put(anim.layerId, anim);
-				lastAnimTranslation = anim;
-				break;
-			case "Lcl Rotation":
-				animRotations.put(anim.layerId, anim);
-				lastAnimRotation = anim;
-				break;
-			case "Lcl Scaling":
-				animScales.put(anim.layerId, anim);
-				lastAnimScale = anim;
-				break;
-			}
-		}
-	}
-	
-	public FbxAnimNode animTranslation(long layerId) {
-		if(layerId == 0)
-			return lastAnimTranslation;
-		return animTranslations.get(layerId);
-	}
-	
-	public FbxAnimNode animRotation(long layerId) {
-		if(layerId == 0)
-			return lastAnimRotation;
-		return animRotations.get(layerId);
-	}
-	
-	public FbxAnimNode animScale(long layerId) {
-		if(layerId == 0)
-			return lastAnimScale;
-		return animScales.get(layerId);
-	}
-	
-	@Override
-	public void link(FbxObject otherObject) {
-		if(otherObject instanceof FbxMaterial) {
-			FbxMaterial m = (FbxMaterial) otherObject;
-			Material mat = m.material;
-			if(cullMode != FaceCullMode.Back)
-				mat.getAdditionalRenderState().setFaceCullMode(cullMode);
-			for(Geometry g : mesh.geometries) {
-				if(g.getUserData("FBXMaterial") != null) {
-					if((Integer) g.getUserData("FBXMaterial") == mesh.lastMaterialId)
-						g.setMaterial(mat);
-				} else {
-					g.setMaterial(mat);
-				}
-			}
-			mesh.lastMaterialId++;
-		} else if(otherObject instanceof FbxNode) {
-			FbxNode n = (FbxNode) otherObject;
-			node.attachChild(n.node);
-			n.parentFbxNode = this;
-			if(isLimb() && n.isLimb()) {
-				if(bone == null)
-					bone = new Bone(name);
-				if(n.bone == null)
-					n.bone = new Bone(n.name);
-				bone.addChild(n.bone);
-			}
-		} else if(otherObject instanceof FbxMesh) {
-			FbxMesh m = (FbxMesh) otherObject;
-			m.setParent(node);
-			m.parent = this;
-			mesh = m;
-		}
-	}
-	
-	public boolean isLimb() {
-		return type.equals("LimbNode");
-	}
+
+    public FaceCullMode cullMode = FaceCullMode.Back;
+    public Transform localTransform;
+    public Node node;
+    public FbxNode parentFbxNode;
+
+    public boolean rotationActive = false;
+    public RotationOrder rotationOrder = RotationOrder.EULER_XYZ;
+
+
+    // For bones and animation, in world space
+    public Matrix4f bindTransform = null;
+    public int boneIndex;
+    public Map<Long, FbxAnimNode> animTranslations = new HashMap<>();
+    public Map<Long, FbxAnimNode> animRotations = new HashMap<>();
+    public Map<Long, FbxAnimNode> animScales = new HashMap<>();
+    public Bone bone;
+    private FbxAnimNode lastAnimTranslation;
+    private FbxAnimNode lastAnimRotation;
+    private FbxAnimNode lastAnimScale;
+    private FbxMesh mesh;
+    public Map<Long, FbxCluster> skinToCluster = new HashMap<>();
+
+    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);
+            switch(propName) {
+            case "RotationOrder":
+                rotationOrder = RotationOrder.values[(Integer) prop.properties.get(4)];
+                break;
+            case "Lcl Translation":
+                readVectorFromProp(translationLocalRaw, prop);
+                break;
+            case "Lcl Rotation":
+                readVectorFromProp(rotationLocalRaw, prop);
+                break;
+            case "Lcl Scaling":
+                readVectorFromProp(scaleLocalRaw, prop);
+                break;
+            case "PreRotation":
+                readVectorFromProp(rotationPreRaw, prop);
+                break;
+            case "RotationActive":
+                rotationActive = ((Number) prop.properties.get(4)).intValue() == 1;
+                break;
+            case "RotationPivot":
+                readVectorFromProp(rotationPivotRaw, prop);
+                break;
+            case "PostRotation":
+                readVectorFromProp(rotationPostRaw, prop);
+                break;
+            case "ScaleOffset":
+                readVectorFromProp(scaleOffsetRaw, prop);
+                break;
+            case "ScalePivot":
+                readVectorFromProp(scalePivotRaw, prop);
+                break;
+            case "U":
+                String userDataKey = (String) prop.properties.get(0);
+                String userDataType = (String) prop.properties.get(1);
+                Object userDataValue;
+                if(userDataType.equals("KString")) {
+                    userDataValue = prop.properties.get(4);
+                } else if(userDataType.equals("int")) {
+                    userDataValue = prop.properties.get(4);
+                } else if(userDataType.equals("double")) {
+                    // NOTE: jME3 does not support doubles in UserData.
+                    //       Need to convert to float.
+                    userDataValue = ((Double) prop.properties.get(4)).floatValue();
+                } else if(userDataType.equals("Vector")) {
+                    x = (Double) prop.properties.get(4);
+                    y = (Double) prop.properties.get(5);
+                    z = (Double) prop.properties.get(6);
+                    userDataValue = new Vector3f((float) x, (float) y, (float) z);
+                } else {
+                    scene.warning("Unsupported user data type: " + userDataType + ". Ignoring.");
+                    continue;
+                }
+                node.setUserData(userDataKey, userDataValue);
+                break;
+            }
+        }
+
+        FbxElement cullingElement = element.getChildById("Culling");
+        if(cullingElement != null && cullingElement.properties.get(0).equals("CullingOff"))
+            cullMode = FaceCullMode.Off; // TODO Add other variants
+
+        /*From http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/the-makeup-of-the-local-matrix-of-an-kfbxnode/
+
+        Local Matrix = LclTranslation * RotationOffset * RotationPivot *
+                PreRotation * LclRotation * PostRotation * RotationPivotInverse *
+                ScalingOffset * ScalingPivot * LclScaling * ScalingPivotInverse
+
+        LocalTranslation : translate (xform -query -translation)
+        RotationOffset: translation compensates for the change in the rotate pivot point (xform -q -rotateTranslation)
+        RotationPivot: current rotate pivot position (xform -q -rotatePivot)
+        PreRotation : joint orientation(pre rotation)
+        LocalRotation: rotate transform (xform -q -rotation & xform -q -rotateOrder)
+        PostRotation : rotate axis (xform -q -rotateAxis)
+        RotationPivotInverse: inverse of RotationPivot
+        ScalingOffset: translation compensates for the change in the scale pivot point (xform -q -scaleTranslation)
+        ScalingPivot: current scale pivot position (xform -q -scalePivot)
+        LocalScaling: scale transform (xform -q -scale)
+        ScalingPivotInverse: inverse of ScalingPivot
+        */
+
+        RotationOrder rotOrder = rotationActive ? rotationOrder : RotationOrder.EULER_XYZ;
+
+        Matrix4f transformMatrix = new Matrix4f();
+        transformMatrix.setTranslation(translationLocalRaw.x + rotationOffsetRaw.x + rotationPivotRaw.x, translationLocalRaw.y + rotationOffsetRaw.y + rotationPivotRaw.y, translationLocalRaw.z + rotationOffsetRaw.z + rotationPivotRaw.z);
+
+        if(rotationActive) {
+            Quaternion postRotation = rotOrder.rotate(rotationPostRaw.x, rotationPostRaw.y, rotationPostRaw.z);
+            Quaternion localRotation = rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z);
+            Quaternion preRotation = rotOrder.rotate(rotationPreRaw.x, rotationPreRaw.y, rotationPreRaw.z);
+            //preRotation.multLocal(localRotation).multLocal(postRotation);
+            postRotation.multLocal(localRotation).multLocal(preRotation);
+            transformMatrix.multLocal(postRotation);
+        } else {
+            transformMatrix.multLocal(rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z));
+        }
+
+        Matrix4f mat = new Matrix4f();
+        mat.setTranslation(scaleOffsetRaw.x + scalePivotRaw.x - rotationPivotRaw.x, scaleOffsetRaw.y + scalePivotRaw.y - rotationPivotRaw.y, scaleOffsetRaw.z + scalePivotRaw.z - rotationPivotRaw.z);
+        transformMatrix.multLocal(mat);
+
+        transformMatrix.scale(scaleLocalRaw);
+        transformMatrix.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize));
+
+        mat.setTranslation(scalePivotRaw.negate());
+        transformMatrix.multLocal(mat);
+
+        localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector());
+
+        node.setLocalTransform(localTransform);
+    }
+
+    @Override
+    public void linkToZero() {
+        scene.sceneNode.attachChild(node);
+    }
+
+    public void setSkeleton(Skeleton skeleton) {
+        if(bone != null)
+            boneIndex = skeleton.getBoneIndex(bone);
+    }
+
+    public void buildBindPoseBoneTransform() {
+        if(bone != null) {
+            Matrix4f t = bindTransform;
+            if(t != null) {
+                Matrix4f parentMatrix = parentFbxNode != null ? parentFbxNode.bindTransform : Matrix4f.IDENTITY;
+                if(parentMatrix == null)
+                    parentMatrix = node.getLocalToWorldMatrix(null);
+                t = parentMatrix.invert().multLocal(t);
+                bone.setBindTransforms(t.toTranslationVector(), t.toRotationQuat(), t.toScaleVector());
+            } else {
+                bone.setBindTransforms(node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale());
+            }
+        }
+    }
+
+    @Override
+    public void link(FbxObject child, String propertyName) {
+        if(child instanceof FbxAnimNode) {
+            FbxAnimNode anim = (FbxAnimNode) child;
+            switch(propertyName) {
+            case "Lcl Translation":
+                animTranslations.put(anim.layerId, anim);
+                lastAnimTranslation = anim;
+                break;
+            case "Lcl Rotation":
+                animRotations.put(anim.layerId, anim);
+                lastAnimRotation = anim;
+                break;
+            case "Lcl Scaling":
+                animScales.put(anim.layerId, anim);
+                lastAnimScale = anim;
+                break;
+            }
+        }
+    }
+
+    public FbxAnimNode animTranslation(long layerId) {
+        if(layerId == 0)
+            return lastAnimTranslation;
+        return animTranslations.get(layerId);
+    }
+
+    public FbxAnimNode animRotation(long layerId) {
+        if(layerId == 0)
+            return lastAnimRotation;
+        return animRotations.get(layerId);
+    }
+
+    public FbxAnimNode animScale(long layerId) {
+        if(layerId == 0)
+            return lastAnimScale;
+        return animScales.get(layerId);
+    }
+
+    @Override
+    public void link(FbxObject otherObject) {
+        if(otherObject instanceof FbxMaterial) {
+            FbxMaterial m = (FbxMaterial) otherObject;
+            Material mat = m.material;
+            if(cullMode != FaceCullMode.Back)
+                mat.getAdditionalRenderState().setFaceCullMode(cullMode);
+            for(Geometry g : mesh.geometries) {
+                if(g.getUserData("FBXMaterial") != null) {
+                    if((Integer) g.getUserData("FBXMaterial") == mesh.lastMaterialId)
+                        g.setMaterial(mat);
+                } else {
+                    g.setMaterial(mat);
+                }
+            }
+            mesh.lastMaterialId++;
+        } else if(otherObject instanceof FbxNode) {
+            FbxNode n = (FbxNode) otherObject;
+            node.attachChild(n.node);
+            n.parentFbxNode = this;
+            if(isLimb() && n.isLimb()) {
+                if(bone == null)
+                    bone = new Bone(name);
+                if(n.bone == null)
+                    n.bone = new Bone(n.name);
+                bone.addChild(n.bone);
+            }
+        } else if(otherObject instanceof FbxMesh) {
+            FbxMesh m = (FbxMesh) otherObject;
+            m.setParent(node);
+            m.parent = this;
+            mesh = m;
+        }
+    }
+
+    public boolean isLimb() {
+        return type.equals("LimbNode");
+    }
 }

+ 32 - 32
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java

@@ -5,36 +5,36 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxObject {
-	
-	protected final SceneLoader scene;
-	public final FbxElement element;
-	public final long id;
-	public final String name;
-	public final String type;
-	
-	public FbxObject(SceneLoader scene, FbxElement element) {
-		this.scene = scene;
-		this.element = element;
-		this.id = (Long) element.properties.get(0);
-		String name = (String) element.properties.get(1);
-		this.name = name.substring(0, name.indexOf(0));
-		this.type = (String) element.properties.get(2);
-	}
-	
-	public void link(FbxObject child) {
-	}
-	
-	public void link(FbxObject child, String propertyName) {
-	}
-	
-	// Parent is 0 id
-	public void linkToZero() {
-	}
-	
-	protected static void readVectorFromProp(Vector3f store, FbxElement propElement) {
-		float x = ((Double) propElement.properties.get(4)).floatValue();
-		float y = ((Double) propElement.properties.get(5)).floatValue();
-		float z = ((Double) propElement.properties.get(6)).floatValue();
-		store.set(x, y, z);
-	}
+
+    protected final SceneLoader scene;
+    public final FbxElement element;
+    public final long id;
+    public final String name;
+    public final String type;
+
+    public FbxObject(SceneLoader scene, FbxElement element) {
+        this.scene = scene;
+        this.element = element;
+        this.id = (Long) element.properties.get(0);
+        String name = (String) element.properties.get(1);
+        this.name = name.substring(0, name.indexOf(0));
+        this.type = (String) element.properties.get(2);
+    }
+
+    public void link(FbxObject child) {
+    }
+
+    public void link(FbxObject child, String propertyName) {
+    }
+
+    // Parent is 0 id
+    public void linkToZero() {
+    }
+
+    protected static void readVectorFromProp(Vector3f store, FbxElement propElement) {
+        float x = ((Double) propElement.properties.get(4)).floatValue();
+        float y = ((Double) propElement.properties.get(5)).floatValue();
+        float z = ((Double) propElement.properties.get(6)).floatValue();
+        store.set(x, y, z);
+    }
 }

+ 132 - 132
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java

@@ -15,136 +15,136 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxSkin extends FbxObject {
-	
-	public String skinningType;
-	public List<FbxMesh> toSkin = new ArrayList<>();
-	public List<FbxNode> bones = new ArrayList<>();
-	
-	public FbxSkin(SceneLoader scene, FbxElement element) {
-		super(scene, element);
-		for(FbxElement e : element.children) {
-			switch(e.id) {
-			case "SkinningType":
-				skinningType = (String) e.properties.get(0);
-				break;
-			}
-		}
-	}
-	
-	@Override
-	public void link(FbxObject otherObject) {
-		if(otherObject instanceof FbxCluster) {
-			FbxCluster cluster = ((FbxCluster) otherObject);
-			cluster.skin = this;
-		}
-	}
-	
-	public void generateSkinning() {
-		for(FbxMesh fbxMesh : toSkin) {
-			if(fbxMesh.geometries == null)
-				continue;
-			Mesh firstMesh = fbxMesh.geometries.get(0).getMesh();
-			int maxWeightsPerVert = generateBoneData(firstMesh, fbxMesh);
-			for(int i = 0; i < fbxMesh.geometries.size(); ++i) {
-				Mesh mesh = fbxMesh.geometries.get(i).getMesh();
-				if(mesh != firstMesh) {
-					mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneWeight));
-					mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneIndex));
-					mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneWeight));
-					mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneIndex));
-				}
-				mesh.setMaxNumWeights(maxWeightsPerVert);
-				mesh.generateBindPose(true);
-			}
-		}
-	}
-	
-	private int generateBoneData(Mesh mesh, FbxMesh fbxMesh) {
-		// Create bone buffers
-		FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(fbxMesh.vCount * 4);
-		ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(fbxMesh.vCount * 4);
-		mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeightData);
-		mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndicesData);
-		mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(Usage.CpuOnly);
-		mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(Usage.CpuOnly);
-		VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight);
-		VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex);
-		indicesHW.setUsage(Usage.CpuOnly); // Setting usage to CpuOnly so that the buffer is not send empty to the GPU
-		weightsHW.setUsage(Usage.CpuOnly);
-		mesh.setBuffer(weightsHW);
-		mesh.setBuffer(indicesHW);
-		int bonesLimitExceeded = 0;
-		// Accumulate skin bones influence into mesh buffers
-		for(FbxNode limb : bones) {
-			FbxCluster cluster = limb.skinToCluster.get(id);
-			if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length)
-				continue;
-			if(limb.boneIndex > 255)
-				throw new AssetLoadException("Bone index can't be packed into byte");
-			for(int i = 0; i < cluster.indexes.length; ++i) {
-				int vertexIndex = cluster.indexes[i];
-				if(vertexIndex >= fbxMesh.reverseVertexMap.size())
-					throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + fbxMesh.reverseVertexMap.size());
-				List<Integer> dstVertices = fbxMesh.reverseVertexMap.get(vertexIndex);
-				for(int j = 0; j < dstVertices.size(); ++j) {
-					int v = dstVertices.get(j);
-					// Append bone index and weight to vertex
-					int offset;
-					int smallestOffset = 0;
-					float w = 0;
-					float smallestW = Float.MAX_VALUE;
-					for(offset = v * 4; offset < v * 4 + 4; ++offset) {
-						w = boneWeightData.get(offset);
-						if(w == 0)
-							break;
-						if(w < smallestW) {
-							smallestW = w;
-							smallestOffset = offset;
-						}
-					}
-					if(w == 0) {
-						boneWeightData.put(offset, (float) cluster.weights[i]);
-						boneIndicesData.put(offset, (byte) limb.boneIndex);
-					} else {
-						if((float) cluster.weights[i] > smallestW) { // If current weight more than smallest, discard smallest
-							boneWeightData.put(smallestOffset, (float) cluster.weights[i]);
-							boneIndicesData.put(smallestOffset, (byte) limb.boneIndex);
-						}
-						bonesLimitExceeded++;
-					}
-				}
-			}
-		}
-		if(bonesLimitExceeded > 0)
-			scene.warning("Skinning support max 4 bone per vertex. Exceeding data of " + bonesLimitExceeded + " weights in mesh bones will be discarded");
-		// Postprocess bones weights
-		int maxWeightsPerVert = 0;
-		boneWeightData.rewind();
-		for(int v = 0; v < fbxMesh.vCount; v++) {
-			float w0 = boneWeightData.get();
-			float w1 = boneWeightData.get();
-			float w2 = boneWeightData.get();
-			float w3 = boneWeightData.get();
-			if(w3 != 0) {
-				maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
-			} else if(w2 != 0) {
-				maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
-			} else if(w1 != 0) {
-				maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
-			} else if(w0 != 0) {
-				maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
-			}
-			float sum = w0 + w1 + w2 + w3;
-			if(sum != 1f) {
-				// normalize weights
-				float mult = (sum != 0) ? (1f / sum) : 0;
-				boneWeightData.position(v * 4);
-				boneWeightData.put(w0 * mult);
-				boneWeightData.put(w1 * mult);
-				boneWeightData.put(w2 * mult);
-				boneWeightData.put(w3 * mult);
-			}
-		}
-		return maxWeightsPerVert;
-	}
+
+    public String skinningType;
+    public List<FbxMesh> toSkin = new ArrayList<>();
+    public List<FbxNode> bones = new ArrayList<>();
+
+    public FbxSkin(SceneLoader scene, FbxElement element) {
+        super(scene, element);
+        for(FbxElement e : element.children) {
+            switch(e.id) {
+            case "SkinningType":
+                skinningType = (String) e.properties.get(0);
+                break;
+            }
+        }
+    }
+    
+    @Override
+    public void link(FbxObject otherObject) {
+        if(otherObject instanceof FbxCluster) {
+            FbxCluster cluster = ((FbxCluster) otherObject);
+            cluster.skin = this;
+        }
+    }
+    
+    public void generateSkinning() {
+        for(FbxMesh fbxMesh : toSkin) {
+            if(fbxMesh.geometries == null)
+                continue;
+            Mesh firstMesh = fbxMesh.geometries.get(0).getMesh();
+            int maxWeightsPerVert = generateBoneData(firstMesh, fbxMesh);
+            for(int i = 0; i < fbxMesh.geometries.size(); ++i) {
+                Mesh mesh = fbxMesh.geometries.get(i).getMesh();
+                if(mesh != firstMesh) {
+                    mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneWeight));
+                    mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneIndex));
+                    mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneWeight));
+                    mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneIndex));
+                }
+                mesh.setMaxNumWeights(maxWeightsPerVert);
+                mesh.generateBindPose(true);
+            }
+        }
+    }
+    
+    private int generateBoneData(Mesh mesh, FbxMesh fbxMesh) {
+        // Create bone buffers
+        FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(fbxMesh.vCount * 4);
+        ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(fbxMesh.vCount * 4);
+        mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeightData);
+        mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndicesData);
+        mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(Usage.CpuOnly);
+        mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(Usage.CpuOnly);
+        VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight);
+        VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex);
+        indicesHW.setUsage(Usage.CpuOnly); // Setting usage to CpuOnly so that the buffer is not send empty to the GPU
+        weightsHW.setUsage(Usage.CpuOnly);
+        mesh.setBuffer(weightsHW);
+        mesh.setBuffer(indicesHW);
+        int bonesLimitExceeded = 0;
+        // Accumulate skin bones influence into mesh buffers
+        for(FbxNode limb : bones) {
+            FbxCluster cluster = limb.skinToCluster.get(id);
+            if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length)
+                continue;
+            if(limb.boneIndex > 255)
+                throw new AssetLoadException("Bone index can't be packed into byte");
+            for(int i = 0; i < cluster.indexes.length; ++i) {
+                int vertexIndex = cluster.indexes[i];
+                if(vertexIndex >= fbxMesh.reverseVertexMap.size())
+                    throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + fbxMesh.reverseVertexMap.size());
+                List<Integer> dstVertices = fbxMesh.reverseVertexMap.get(vertexIndex);
+                for(int j = 0; j < dstVertices.size(); ++j) {
+                    int v = dstVertices.get(j);
+                    // Append bone index and weight to vertex
+                    int offset;
+                    int smallestOffset = 0;
+                    float w = 0;
+                    float smallestW = Float.MAX_VALUE;
+                    for(offset = v * 4; offset < v * 4 + 4; ++offset) {
+                        w = boneWeightData.get(offset);
+                        if(w == 0)
+                            break;
+                        if(w < smallestW) {
+                            smallestW = w;
+                            smallestOffset = offset;
+                        }
+                    }
+                    if(w == 0) {
+                        boneWeightData.put(offset, (float) cluster.weights[i]);
+                        boneIndicesData.put(offset, (byte) limb.boneIndex);
+                    } else {
+                        if((float) cluster.weights[i] > smallestW) { // If current weight more than smallest, discard smallest
+                            boneWeightData.put(smallestOffset, (float) cluster.weights[i]);
+                            boneIndicesData.put(smallestOffset, (byte) limb.boneIndex);
+                        }
+                        bonesLimitExceeded++;
+                    }
+                }
+            }
+        }
+        if(bonesLimitExceeded > 0)
+            scene.warning("Skinning support max 4 bone per vertex. Exceeding data of " + bonesLimitExceeded + " weights in mesh bones will be discarded");
+        // Postprocess bones weights
+        int maxWeightsPerVert = 0;
+        boneWeightData.rewind();
+        for(int v = 0; v < fbxMesh.vCount; v++) {
+            float w0 = boneWeightData.get();
+            float w1 = boneWeightData.get();
+            float w2 = boneWeightData.get();
+            float w3 = boneWeightData.get();
+            if(w3 != 0) {
+                maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
+            } else if(w2 != 0) {
+                maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
+            } else if(w1 != 0) {
+                maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
+            } else if(w0 != 0) {
+                maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
+            }
+            float sum = w0 + w1 + w2 + w3;
+            if(sum != 1f) {
+                // normalize weights
+                float mult = (sum != 0) ? (1f / sum) : 0;
+                boneWeightData.position(v * 4);
+                boneWeightData.put(w0 * mult);
+                boneWeightData.put(w1 * mult);
+                boneWeightData.put(w2 * mult);
+                boneWeightData.put(w3 * mult);
+            }
+        }
+        return maxWeightsPerVert;
+    }
 }

+ 31 - 31
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java

@@ -7,36 +7,36 @@ import com.jme3.scene.plugins.fbx.SceneLoader;
 import com.jme3.scene.plugins.fbx.file.FbxElement;
 
 public class FbxTexture extends FbxObject {
-	
-	String bindType;
-	String filename;
-	
-	public Texture texture;
-	
-	public FbxTexture(SceneLoader scene, FbxElement element) {
-		super(scene, element);
-		for(FbxElement e : element.children) {
-			switch(e.id) {
-			case "Type":
-				bindType = (String) e.properties.get(0);
-				break;
-			case "FileName":
-				filename = (String) e.properties.get(0);
-				break;
-			}
-		}
-		texture = new Texture2D();
-		texture.setName(name);
-		texture.setWrap(WrapMode.Repeat); // Default FBX wrapping. TODO: Investigate where this is stored (probably, in material)
-	}
 
-	@Override
-	public void link(FbxObject otherObject) {
-		if(otherObject instanceof FbxImage) {
-			FbxImage img = (FbxImage) otherObject;
-			if(img.image == null)
-				return;
-			texture.setImage(img.image);
-		}
-	}
+    String bindType;
+    String filename;
+
+    public Texture texture;
+
+    public FbxTexture(SceneLoader scene, FbxElement element) {
+        super(scene, element);
+        for(FbxElement e : element.children) {
+            switch(e.id) {
+            case "Type":
+                bindType = (String) e.properties.get(0);
+                break;
+            case "FileName":
+                filename = (String) e.properties.get(0);
+                break;
+            }
+        }
+        texture = new Texture2D();
+        texture.setName(name);
+        texture.setWrap(WrapMode.Repeat); // Default FBX wrapping. TODO: Investigate where this is stored (probably, in material)
+    }
+
+    @Override
+    public void link(FbxObject otherObject) {
+        if(otherObject instanceof FbxImage) {
+            FbxImage img = (FbxImage) otherObject;
+            if(img.image == null)
+                return;
+            texture.setImage(img.image);
+        }
+    }
 }

+ 5 - 5
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java

@@ -378,11 +378,11 @@ public class GltfUtils {
     public static float readAsFloat(LittleEndien stream, VertexBuffer.Format format) throws IOException {
         //We may have packed data so depending on the format, we need to read data differently and unpack it
         // Implementations must use following equations to get corresponding floating-point value f from a normalized integer c and vise-versa:
-        // accessor.componentType	int-to-float	            float-to-int
-        // 5120 (BYTE)	            f = max(c / 127.0, -1.0)	c = round(f * 127.0)
-        // 5121 (UNSIGNED_BYTE)	    f = c / 255.0	            c = round(f * 255.0)
-        // 5122 (SHORT)	            f = max(c / 32767.0, -1.0)	c = round(f * 32767.0)
-        // 5123 (UNSIGNED_SHORT)	f = c / 65535.0	            c = round(f * 65535.0)
+        // accessor.componentType    int-to-float                float-to-int
+        // 5120 (BYTE)               f = max(c / 127.0, -1.0)    c = round(f * 127.0)
+        // 5121 (UNSIGNED_BYTE)      f = c / 255.0               c = round(f * 255.0)
+        // 5122 (SHORT)              f = max(c / 32767.0, -1.0)  c = round(f * 32767.0)
+        // 5123 (UNSIGNED_SHORT)     f = c / 65535.0             c = round(f * 65535.0)
         byte b;
         switch (format) {
             case Byte:

+ 7 - 7
jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java

@@ -99,7 +99,7 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
     }
 
     private void reset() {
-    	meshLoader.reset();
+        meshLoader.reset();
         elementStack.clear();
         nodeIdx = 0;
 
@@ -300,12 +300,12 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
         entityNode = new com.jme3.scene.Node(name);
         OgreMeshKey meshKey = new OgreMeshKey(meshFile, materialList);
         try {
-			try{
-				Spatial ogreMesh=(Spatial)meshLoader.load(assetManager.locateAsset(meshKey));
-				entityNode.attachChild(ogreMesh);
-			}catch(IOException e){
-				throw new AssetNotFoundException(meshKey.toString());
-			}
+            try{
+                Spatial ogreMesh=(Spatial)meshLoader.load(assetManager.locateAsset(meshKey));
+                entityNode.attachChild(ogreMesh);
+            }catch(IOException e){
+                throw new AssetNotFoundException(meshKey.toString());
+            }
         } catch (AssetNotFoundException ex) {
             if (ex.getMessage().equals(meshFile)) {
                 logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{meshKey, key});

+ 13 - 13
jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMeshLoader.java

@@ -9,18 +9,18 @@ import com.jme3.asset.AssetKey;
 import com.jme3.scene.Spatial;
 
 public class SceneMeshLoader extends MeshLoader{
-	private Map<AssetKey,Spatial> cache=new HashMap<>();
-	@Override
+    private Map<AssetKey,Spatial> cache=new HashMap<>();
+    @Override
     public Object load(AssetInfo info) throws IOException {
-		AssetKey key=info.getKey();
-		Spatial output=cache.get(key);
-		if(output==null){
-			output=(Spatial)super.load(info);
-			cache.put(key,output);
-		}
-		return output.clone(false);
-	}
-	public void reset(){
-		cache.clear();
-	}
+        AssetKey key=info.getKey();
+        Spatial output=cache.get(key);
+        if(output==null){
+            output=(Spatial)super.load(info);
+            cache.put(key,output);
+        }
+        return output.clone(false);
+    }
+    public void reset(){
+        cache.clear();
+    }
 }

+ 3 - 3
jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java

@@ -50,11 +50,11 @@ import javax.xml.parsers.ParserConfigurationException;
  */
 public class XMLExporter implements JmeExporter {
     
-    public static final String ELEMENT_MAPENTRY = "MapEntry";	
-    public static final String ELEMENT_KEY = "Key";	
+    public static final String ELEMENT_MAPENTRY = "MapEntry";
+    public static final String ELEMENT_KEY = "Key";
     public static final String ELEMENT_VALUE = "Value";
     public static final String ELEMENT_FLOATBUFFER = "FloatBuffer";
-    public static final String ATTRIBUTE_SIZE = "size";		
+    public static final String ATTRIBUTE_SIZE = "size";
 
     private DOMOutputCapsule domOut;