Reworked original FBX importer
This commit is contained in:
parent
8899bec0cb
commit
f65dd517c9
@ -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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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…
x
Reference in New Issue
Block a user