Explorar o código

Merge pull request #488 from GreenCubes/master

Reworked original FBX importer
Kirill Vainer %!s(int64=9) %!d(string=hai) anos
pai
achega
8804edf396

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

@@ -0,0 +1,52 @@
+package com.jme3.scene.plugins.fbx;
+
+import com.jme3.math.FastMath;
+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 an oridinal 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("Spheric rotation is unsupported in this importer");
+		}
+	}
+	
+	private static Quaternion toQuat(float ax1v, Vector3f ax1, float ax2v, Vector3f ax2, float ax3v, Vector3f ax3) {
+		// TODO It has some potential in optimization
+		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);
+	}
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 182 - 1141
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java


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

@@ -0,0 +1,49 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+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 = (float) (keyTimes[i] - keyTimes[i - 1]);
+					float dtInt = (float) (time - keyTimes[i - 1]);
+					float dv = keyValues[i] - keyValues[i - 1];
+					return keyValues[i - 1] + dv * (dtInt / dt);
+				}
+			}
+		}
+		// right from the whole range
+		return defaultValue;//keyValues[keyValues.length - 1];
+	}
+}

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

@@ -0,0 +1,86 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import java.util.Collection;
+
+import com.jme3.math.Vector3f;
+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);
+	}
+}

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

@@ -0,0 +1,55 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+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;
+	}
+}

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

@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+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);
+		}
+	}
+}

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

@@ -0,0 +1,123 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import java.io.File;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+import com.jme3.scene.plugins.fbx.ContentTextureKey;
+import com.jme3.scene.plugins.fbx.ContentTextureLocator;
+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 aquire 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) ((long) 1 * (long) 1 * (long) Image.Format.RGB8.getBitsPerPixel() / 8L)), ColorSpace.Linear);
+		return image;
+	}
+}

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

@@ -0,0 +1,113 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.plugins.fbx.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 "Aplha Test"
+		m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
+		return m;
+	}
+	
+}

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

@@ -0,0 +1,537 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.jme3.asset.AssetLoadException;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.plugins.fbx.SceneLoader;
+import com.jme3.scene.plugins.fbx.file.FbxElement;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.Node;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+
+public class FbxMesh extends FbxObject {
+	
+	public double[] vertices;
+	public int[] indices;
+	public int[] edges;
+	public String normalsMapping;
+	public String normalsReference;
+	public 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<Integer>(1);
+				l.add(i);
+				reverseVertexMap.add(l);
+			}
+		}
+		if(vertices != null) {
+			// Unroll vertices data array
+			FloatBuffer posBuf = BufferUtils.createFloatBuffer(vCount * 3);
+			mesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
+			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;
+				posBuf.put(x).put(y).put(z);
+			}
+		}
+		if(normals != null) {
+			// Unroll normals data array
+			FloatBuffer normBuf = BufferUtils.createFloatBuffer(vCount * 3);
+			mesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf);
+			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;
+				normBuf.put(x).put(y).put(z);
+			}
+		}
+		if(tangents != null) {
+			// Unroll normals data array
+			FloatBuffer tanBuf = BufferUtils.createFloatBuffer(vCount * 4);
+			mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tanBuf);
+			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;
+				tanBuf.put(x).put(y).put(z).put(-1.0f);
+			}
+		}
+		if(binormals != null) {
+			// Unroll normals data array
+			FloatBuffer binormBuf = BufferUtils.createFloatBuffer(vCount * 3);
+			mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormBuf);
+			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;
+				binormBuf.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 tcBuf = 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, tcBuf);
+			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;
+				tcBuf.put(u).put(v);
+			}
+		}
+		List<Geometry> geometries = new ArrayList<Geometry>();
+		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;
+	}
+}

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

@@ -0,0 +1,272 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.plugins.fbx.RotationOrder;
+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 = (String) prop.properties.get(4);
+				} else if(userDataType.equals("int")) {
+					userDataValue = (Integer) 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");
+	}
+}

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

@@ -0,0 +1,40 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import com.jme3.math.Vector3f;
+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);
+	}
+}

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

@@ -0,0 +1,150 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.jme3.asset.AssetLoadException;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+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 smalestOffset = 0;
+					float w = 0;
+					float smalestW = Float.MAX_VALUE;
+					for(offset = v * 4; offset < v * 4 + 4; ++offset) {
+						w = boneWeightData.get(offset);
+						if(w == 0)
+							break;
+						if(w < smalestW) {
+							smalestW = w;
+							smalestOffset = offset;
+						}
+					}
+					if(w == 0) {
+						boneWeightData.put(offset, (float) cluster.weights[i]);
+						boneIndicesData.put(offset, (byte) limb.boneIndex);
+					} else {
+						if((float) cluster.weights[i] > smalestW) { // If current weight more than smallest, discard smallest
+							boneWeightData.put(smalestOffset, (float) cluster.weights[i]);
+							boneIndicesData.put(smalestOffset, (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;
+	}
+}

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

@@ -0,0 +1,42 @@
+package com.jme3.scene.plugins.fbx.objects;
+
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.Texture.WrapMode;
+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);
+		}
+	}
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio