Reworked original FBX importer

define_list_fix
Eirenliel Allier 9 years ago
parent 8899bec0cb
commit f65dd517c9
  1. 102
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java
  2. 1641
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
  3. 49
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java
  4. 86
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java
  5. 55
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java
  6. 42
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java
  7. 123
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java
  8. 113
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java
  9. 537
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java
  10. 274
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java
  11. 40
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java
  12. 150
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java
  13. 42
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java

@ -0,0 +1,102 @@
package com.jme3.scene.plugins.fbx;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
// TODO This class has some potential in it... Should investigate
public enum RotationOrder {
EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ;
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:
throw new IllegalArgumentException("Spheric rotation is unsupported in this importer");
}
/*float c1 = FastMath.cos( x / 2 );
float c2 = FastMath.cos( y / 2 );
float c3 = FastMath.cos( z / 2 );
float s1 = FastMath.sin( x / 2 );
float s2 = FastMath.sin( y / 2 );
float s3 = FastMath.sin( z / 2 );
float _x;
float _y;
float _z;
float _w;
switch(order) {
case EULER_XYZ:
_x = s1 * c2 * c3 + c1 * s2 * s3;
_y = c1 * s2 * c3 - s1 * c2 * s3;
_z = c1 * c2 * s3 + s1 * s2 * c3;
_w = c1 * c2 * c3 - s1 * s2 * s3;
return new Quaternion(_x, _y, _z, _w);
case EULER_YXZ:
_x = s1 * c2 * c3 + c1 * s2 * s3;
_y = c1 * s2 * c3 - s1 * c2 * s3;
_z = c1 * c2 * s3 - s1 * s2 * c3;
_w = c1 * c2 * c3 + s1 * s2 * s3;
return new Quaternion(_x, _y, _z, _w);
case EULER_ZXY:
_x = s1 * c2 * c3 - c1 * s2 * s3;
_y = c1 * s2 * c3 + s1 * c2 * s3;
_z = c1 * c2 * s3 + s1 * s2 * c3;
_w = c1 * c2 * c3 - s1 * s2 * s3;
return new Quaternion(_x, _y, _z, _w);
case EULER_ZYX:
_x = s1 * c2 * c3 - c1 * s2 * s3;
_y = c1 * s2 * c3 + s1 * c2 * s3;
_z = c1 * c2 * s3 - s1 * s2 * c3;
_w = c1 * c2 * c3 + s1 * s2 * s3;
return new Quaternion(_x, _y, _z, _w);
case EULER_YZX:
_x = s1 * c2 * c3 + c1 * s2 * s3;
_y = c1 * s2 * c3 + s1 * c2 * s3;
_z = c1 * c2 * s3 - s1 * s2 * c3;
_w = c1 * c2 * c3 - s1 * s2 * s3;
return new Quaternion(_x, _y, _z, _w);
case EULER_XZY:
_x = s1 * c2 * c3 - c1 * s2 * s3;
_y = c1 * s2 * c3 - s1 * c2 * s3;
_z = c1 * c2 * s3 + s1 * s2 * c3;
_w = c1 * c2 * c3 + s1 * s2 * s3;
return new Quaternion(_x, _y, _z, _w);
}
*/
throw new AssertionError("Impossible");
}
private static Quaternion toQuat(float ax1v, Vector3f ax1, float ax2v, Vector3f ax2, float ax3v, Vector3f ax3) {
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);
}
}

@ -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];
}
}

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

@ -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;
}
}

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

@ -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;
}
}

@ -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;
}
}

@ -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<>());
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;
}
}

@ -0,0 +1,274 @@
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 Map<String, Object> userData = new HashMap<>();
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);
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;
}
userData.put(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 = new Node(name);
userData.forEach((s, o) -> node.setUserData(s, o));
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");
}
}

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

@ -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;
}
}

@ -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);
}
}
}
Loading…
Cancel
Save