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…
Reference in new issue