commit
8804edf396
@ -0,0 +1,52 @@ |
|||||||
|
package com.jme3.scene.plugins.fbx; |
||||||
|
|
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
|
||||||
|
public enum RotationOrder { |
||||||
|
|
||||||
|
EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ; |
||||||
|
|
||||||
|
// Static values field for fast access by an oridinal without Enum.values() overhead
|
||||||
|
public static final RotationOrder[] values = values(); |
||||||
|
|
||||||
|
private RotationOrder() { |
||||||
|
} |
||||||
|
|
||||||
|
public Quaternion rotate(Vector3f vec) { |
||||||
|
return fromEuler(vec.x * FastMath.DEG_TO_RAD, vec.y * FastMath.DEG_TO_RAD, vec.z * FastMath.DEG_TO_RAD, this); |
||||||
|
} |
||||||
|
|
||||||
|
public Quaternion rotate(float x, float y, float z) { |
||||||
|
return fromEuler(x * FastMath.DEG_TO_RAD, y * FastMath.DEG_TO_RAD, z * FastMath.DEG_TO_RAD, this); |
||||||
|
} |
||||||
|
|
||||||
|
private static Quaternion fromEuler(float x, float y, float z, RotationOrder order) { |
||||||
|
switch(order) { |
||||||
|
case EULER_XYZ: |
||||||
|
return toQuat(x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z); |
||||||
|
case EULER_YXZ: |
||||||
|
return toQuat(y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z); |
||||||
|
case EULER_ZXY: |
||||||
|
return toQuat(z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y); |
||||||
|
case EULER_ZYX: |
||||||
|
return toQuat(z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X); |
||||||
|
case EULER_YZX: |
||||||
|
return toQuat(y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X); |
||||||
|
case EULER_XZY: |
||||||
|
return toQuat(x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y); |
||||||
|
case SPHERIC_XYZ: |
||||||
|
default: |
||||||
|
throw new IllegalArgumentException("Spheric rotation is unsupported in this importer"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static Quaternion toQuat(float ax1v, Vector3f ax1, float ax2v, Vector3f ax2, float ax3v, Vector3f ax3) { |
||||||
|
// TODO It has some potential in optimization
|
||||||
|
Quaternion q1 = new Quaternion().fromAngleNormalAxis(ax1v, ax1); |
||||||
|
Quaternion q2 = new Quaternion().fromAngleNormalAxis(ax2v, ax2); |
||||||
|
Quaternion q3 = new Quaternion().fromAngleNormalAxis(ax3v, ax3); |
||||||
|
return q1.multLocal(q2).multLocal(q3); |
||||||
|
} |
||||||
|
} |
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<Integer>()); |
||||||
|
for(int i = 0; i < vCount; ++i) { |
||||||
|
int index = vertexMap.get(i); |
||||||
|
reverseVertexMap.get(index).add(i); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// Stub for no vertex indexing (direct mapping)
|
||||||
|
iCount = vCount = srcVertexCount; |
||||||
|
vertexMap = new ArrayList<>(vCount); |
||||||
|
indexMap = new ArrayList<>(vCount); |
||||||
|
reverseVertexMap = new ArrayList<>(vCount); |
||||||
|
for(int i = 0; i < vCount; ++i) { |
||||||
|
vertexMap.set(i, i); |
||||||
|
indexMap.set(i, i); |
||||||
|
List<Integer> l = new ArrayList<Integer>(1); |
||||||
|
l.add(i); |
||||||
|
reverseVertexMap.add(l); |
||||||
|
} |
||||||
|
} |
||||||
|
if(vertices != null) { |
||||||
|
// Unroll vertices data array
|
||||||
|
FloatBuffer posBuf = BufferUtils.createFloatBuffer(vCount * 3); |
||||||
|
mesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf); |
||||||
|
int srcCount = vertices.length / 3; |
||||||
|
for(int i = 0; i < vCount; ++i) { |
||||||
|
int index = vertexMap.get(i); |
||||||
|
if(index > srcCount) |
||||||
|
throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount); |
||||||
|
float x = (float) vertices[3 * index + 0] / scene.unitSize * scene.xAxis; // XXX Why we should scale by unit size?
|
||||||
|
float y = (float) vertices[3 * index + 1] / scene.unitSize * scene.yAxis; |
||||||
|
float z = (float) vertices[3 * index + 2] / scene.unitSize * scene.zAxis; |
||||||
|
posBuf.put(x).put(y).put(z); |
||||||
|
} |
||||||
|
} |
||||||
|
if(normals != null) { |
||||||
|
// Unroll normals data array
|
||||||
|
FloatBuffer normBuf = BufferUtils.createFloatBuffer(vCount * 3); |
||||||
|
mesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf); |
||||||
|
List<Integer> mapping = null; |
||||||
|
if(normalsMapping.equals("ByVertice")) |
||||||
|
mapping = vertexMap; |
||||||
|
else if(normalsMapping.equals("ByPolygonVertex")) |
||||||
|
mapping = indexMap; |
||||||
|
else |
||||||
|
throw new IOException("Unknown normals mapping type: " + normalsMapping); |
||||||
|
int srcCount = normals.length / 3; |
||||||
|
for(int i = 0; i < vCount; ++i) { |
||||||
|
int index = mapping.get(i); |
||||||
|
if(index > srcCount) |
||||||
|
throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount); |
||||||
|
float x = (float) normals[3 * index + 0] * scene.xAxis; |
||||||
|
float y = (float) normals[3 * index + 1] * scene.yAxis; |
||||||
|
float z = (float) normals[3 * index + 2] * scene.zAxis; |
||||||
|
normBuf.put(x).put(y).put(z); |
||||||
|
} |
||||||
|
} |
||||||
|
if(tangents != null) { |
||||||
|
// Unroll normals data array
|
||||||
|
FloatBuffer tanBuf = BufferUtils.createFloatBuffer(vCount * 4); |
||||||
|
mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tanBuf); |
||||||
|
List<Integer> mapping = null; |
||||||
|
if(tangentsMapping.equals("ByVertice")) |
||||||
|
mapping = vertexMap; |
||||||
|
else if(tangentsMapping.equals("ByPolygonVertex")) |
||||||
|
mapping = indexMap; |
||||||
|
else |
||||||
|
throw new IOException("Unknown tangents mapping type: " + tangentsMapping); |
||||||
|
int srcCount = tangents.length / 3; |
||||||
|
for(int i = 0; i < vCount; ++i) { |
||||||
|
int index = mapping.get(i); |
||||||
|
if(index > srcCount) |
||||||
|
throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount); |
||||||
|
float x = (float) tangents[3 * index + 0] * scene.xAxis; |
||||||
|
float y = (float) tangents[3 * index + 1] * scene.yAxis; |
||||||
|
float z = (float) tangents[3 * index + 2] * scene.zAxis; |
||||||
|
tanBuf.put(x).put(y).put(z).put(-1.0f); |
||||||
|
} |
||||||
|
} |
||||||
|
if(binormals != null) { |
||||||
|
// Unroll normals data array
|
||||||
|
FloatBuffer binormBuf = BufferUtils.createFloatBuffer(vCount * 3); |
||||||
|
mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormBuf); |
||||||
|
List<Integer> mapping = null; |
||||||
|
if(binormalsMapping.equals("ByVertice")) |
||||||
|
mapping = vertexMap; |
||||||
|
else if(binormalsMapping.equals("ByPolygonVertex")) |
||||||
|
mapping = indexMap; |
||||||
|
else |
||||||
|
throw new IOException("Unknown binormals mapping type: " + binormalsMapping); |
||||||
|
int srcCount = binormals.length / 3; |
||||||
|
for(int i = 0; i < vCount; ++i) { |
||||||
|
int index = mapping.get(i); |
||||||
|
if(index > srcCount) |
||||||
|
throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount); |
||||||
|
float x = (float) binormals[3 * index + 0] * scene.xAxis; |
||||||
|
float y = (float) binormals[3 * index + 1] * scene.yAxis; |
||||||
|
float z = (float) binormals[3 * index + 2] * scene.zAxis; |
||||||
|
binormBuf.put(x).put(y).put(z); |
||||||
|
} |
||||||
|
} |
||||||
|
for(int uvLayer = 0; uvLayer < uvs.size(); ++uvLayer) { |
||||||
|
double[] uv = uvs.get(uvLayer); |
||||||
|
int[] uvIndex = uvIndexes.size() > uvLayer ? uvIndexes.get(uvLayer) : null; |
||||||
|
List<Integer> unIndexMap = vertexMap; |
||||||
|
if(uvIndex != null) { |
||||||
|
int uvIndexSrcCount = uvIndex.length; |
||||||
|
if(uvIndexSrcCount != iCount) |
||||||
|
throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + iCount); |
||||||
|
// Unroll UV index array
|
||||||
|
unIndexMap = new ArrayList<>(vCount); |
||||||
|
int polyVertCount = 0; |
||||||
|
for(int i = 0; i < iCount; ++i) { |
||||||
|
int index = indices[i]; |
||||||
|
polyVertCount++; |
||||||
|
if(index < 0) { |
||||||
|
if(polyVertCount == 3) { |
||||||
|
unIndexMap.add(uvIndex[i - 2]); |
||||||
|
unIndexMap.add(uvIndex[i - 1]); |
||||||
|
unIndexMap.add(uvIndex[i - 0]); |
||||||
|
} else if(polyVertCount == 4) { |
||||||
|
unIndexMap.add(uvIndex[i - 3]); |
||||||
|
unIndexMap.add(uvIndex[i - 2]); |
||||||
|
unIndexMap.add(uvIndex[i - 1]); |
||||||
|
unIndexMap.add(uvIndex[i - 3]); |
||||||
|
unIndexMap.add(uvIndex[i - 1]); |
||||||
|
unIndexMap.add(uvIndex[i - 0]); |
||||||
|
} |
||||||
|
polyVertCount = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// Unroll UV data array
|
||||||
|
FloatBuffer tcBuf = BufferUtils.createFloatBuffer(vCount * 2); |
||||||
|
VertexBuffer.Type type = VertexBuffer.Type.TexCoord; |
||||||
|
switch(uvLayer) { |
||||||
|
case 1: |
||||||
|
type = VertexBuffer.Type.TexCoord2; |
||||||
|
break; |
||||||
|
case 2: |
||||||
|
type = VertexBuffer.Type.TexCoord3; |
||||||
|
break; |
||||||
|
case 3: |
||||||
|
type = VertexBuffer.Type.TexCoord4; |
||||||
|
break; |
||||||
|
case 4: |
||||||
|
type = VertexBuffer.Type.TexCoord5; |
||||||
|
break; |
||||||
|
case 5: |
||||||
|
type = VertexBuffer.Type.TexCoord6; |
||||||
|
break; |
||||||
|
case 6: |
||||||
|
type = VertexBuffer.Type.TexCoord7; |
||||||
|
break; |
||||||
|
case 7: |
||||||
|
type = VertexBuffer.Type.TexCoord8; |
||||||
|
break; |
||||||
|
} |
||||||
|
mesh.setBuffer(type, 2, tcBuf); |
||||||
|
int srcCount = uv.length / 2; |
||||||
|
for(int i = 0; i < vCount; ++i) { |
||||||
|
int index = unIndexMap.get(i); |
||||||
|
if(index > srcCount) |
||||||
|
throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount); |
||||||
|
float u = (index >= 0) ? (float) uv[2 * index + 0] : 0; |
||||||
|
float v = (index >= 0) ? (float) uv[2 * index + 1] : 0; |
||||||
|
tcBuf.put(u).put(v); |
||||||
|
} |
||||||
|
} |
||||||
|
List<Geometry> geometries = new ArrayList<Geometry>(); |
||||||
|
if(materialsReference.equals("IndexToDirect") && materialsMapping.equals("ByPolygon")) { |
||||||
|
IntMap<List<Integer>> indexBuffers = new IntMap<>(); |
||||||
|
for(int polygon = 0; polygon < materials.length; ++polygon) { |
||||||
|
int material = materials[polygon]; |
||||||
|
List<Integer> list = indexBuffers.get(material); |
||||||
|
if(list == null) { |
||||||
|
list = new ArrayList<>(); |
||||||
|
indexBuffers.put(material, list); |
||||||
|
} |
||||||
|
list.add(polygon * 3 + 0); |
||||||
|
list.add(polygon * 3 + 1); |
||||||
|
list.add(polygon * 3 + 2); |
||||||
|
} |
||||||
|
Iterator<Entry<List<Integer>>> iterator = indexBuffers.iterator(); |
||||||
|
while(iterator.hasNext()) { |
||||||
|
Entry<List<Integer>> e = iterator.next(); |
||||||
|
int materialId = e.getKey(); |
||||||
|
List<Integer> indexes = e.getValue(); |
||||||
|
Mesh newMesh = mesh.clone(); |
||||||
|
newMesh.setBuffer(VertexBuffer.Type.Index, 3, toArray(indexes.toArray(new Integer[indexes.size()]))); |
||||||
|
newMesh.setStatic(); |
||||||
|
newMesh.updateBound(); |
||||||
|
newMesh.updateCounts(); |
||||||
|
Geometry geom = new Geometry(); |
||||||
|
geom.setMesh(newMesh); |
||||||
|
geometries.add(geom); |
||||||
|
geom.setUserData("FBXMaterial", materialId); |
||||||
|
} |
||||||
|
} else { |
||||||
|
mesh.setStatic(); |
||||||
|
mesh.updateBound(); |
||||||
|
mesh.updateCounts(); |
||||||
|
Geometry geom = new Geometry(); |
||||||
|
geom.setMesh(mesh); |
||||||
|
geometries.add(geom); |
||||||
|
} |
||||||
|
return geometries; |
||||||
|
} |
||||||
|
|
||||||
|
private static int[] toArray(Integer[] arr) { |
||||||
|
int[] ret = new int[arr.length]; |
||||||
|
for(int i = 0; i < arr.length; ++i) |
||||||
|
ret[i] = arr[i].intValue(); |
||||||
|
return ret; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,272 @@ |
|||||||
|
package com.jme3.scene.plugins.fbx.objects; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.material.RenderState.FaceCullMode; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.plugins.fbx.RotationOrder; |
||||||
|
import com.jme3.scene.plugins.fbx.SceneLoader; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
|
||||||
|
public class FbxNode extends FbxObject { |
||||||
|
|
||||||
|
public FaceCullMode cullMode = FaceCullMode.Back; |
||||||
|
public Transform localTransform; |
||||||
|
public Node node; |
||||||
|
public FbxNode parentFbxNode; |
||||||
|
|
||||||
|
public boolean rotationActive = false; |
||||||
|
public RotationOrder rotationOrder = RotationOrder.EULER_XYZ; |
||||||
|
|
||||||
|
|
||||||
|
// For bones and animation, in world space
|
||||||
|
public Matrix4f bindTransform = null; |
||||||
|
public int boneIndex; |
||||||
|
public Map<Long, FbxAnimNode> animTranslations = new HashMap<>(); |
||||||
|
public Map<Long, FbxAnimNode> animRotations = new HashMap<>(); |
||||||
|
public Map<Long, FbxAnimNode> animScales = new HashMap<>(); |
||||||
|
public Bone bone; |
||||||
|
private FbxAnimNode lastAnimTranslation; |
||||||
|
private FbxAnimNode lastAnimRotation; |
||||||
|
private FbxAnimNode lastAnimScale; |
||||||
|
private FbxMesh mesh; |
||||||
|
public Map<Long, FbxCluster> skinToCluster = new HashMap<>(); |
||||||
|
|
||||||
|
public FbxNode(SceneLoader scene, FbxElement element) { |
||||||
|
super(scene, element); |
||||||
|
node = new Node(name); |
||||||
|
Vector3f translationLocalRaw = new Vector3f(); |
||||||
|
Vector3f rotationOffsetRaw = new Vector3f(); |
||||||
|
Vector3f rotationPivotRaw = new Vector3f(); |
||||||
|
Vector3f rotationPreRaw = new Vector3f(); |
||||||
|
Vector3f rotationLocalRaw = new Vector3f(); |
||||||
|
Vector3f rotationPostRaw = new Vector3f(); |
||||||
|
Vector3f scaleOffsetRaw = new Vector3f(); |
||||||
|
Vector3f scalePivotRaw = new Vector3f(); |
||||||
|
Vector3f scaleLocalRaw = new Vector3f(1, 1, 1); |
||||||
|
for(FbxElement prop : element.getFbxProperties()) { |
||||||
|
double x, y, z; |
||||||
|
String propName = (String) prop.properties.get(0); |
||||||
|
switch(propName) { |
||||||
|
case "RotationOrder": |
||||||
|
rotationOrder = RotationOrder.values[(Integer) prop.properties.get(4)]; |
||||||
|
break; |
||||||
|
case "Lcl Translation": |
||||||
|
readVectorFromProp(translationLocalRaw, prop); |
||||||
|
break; |
||||||
|
case "Lcl Rotation": |
||||||
|
readVectorFromProp(rotationLocalRaw, prop); |
||||||
|
break; |
||||||
|
case "Lcl Scaling": |
||||||
|
readVectorFromProp(scaleLocalRaw, prop); |
||||||
|
break; |
||||||
|
case "PreRotation": |
||||||
|
readVectorFromProp(rotationPreRaw, prop); |
||||||
|
break; |
||||||
|
case "RotationActive": |
||||||
|
rotationActive = ((Number) prop.properties.get(4)).intValue() == 1; |
||||||
|
break; |
||||||
|
case "RotationPivot": |
||||||
|
readVectorFromProp(rotationPivotRaw, prop); |
||||||
|
break; |
||||||
|
case "PostRotation": |
||||||
|
readVectorFromProp(rotationPostRaw, prop); |
||||||
|
break; |
||||||
|
case "ScaleOffset": |
||||||
|
readVectorFromProp(scaleOffsetRaw, prop); |
||||||
|
break; |
||||||
|
case "ScalePivot": |
||||||
|
readVectorFromProp(scalePivotRaw, prop); |
||||||
|
break; |
||||||
|
case "U": |
||||||
|
String userDataKey = (String) prop.properties.get(0); |
||||||
|
String userDataType = (String) prop.properties.get(1); |
||||||
|
Object userDataValue; |
||||||
|
if(userDataType.equals("KString")) { |
||||||
|
userDataValue = (String) prop.properties.get(4); |
||||||
|
} else if(userDataType.equals("int")) { |
||||||
|
userDataValue = (Integer) prop.properties.get(4); |
||||||
|
} else if(userDataType.equals("double")) { |
||||||
|
// NOTE: jME3 does not support doubles in UserData.
|
||||||
|
// Need to convert to float.
|
||||||
|
userDataValue = ((Double) prop.properties.get(4)).floatValue(); |
||||||
|
} else if(userDataType.equals("Vector")) { |
||||||
|
x = (Double) prop.properties.get(4); |
||||||
|
y = (Double) prop.properties.get(5); |
||||||
|
z = (Double) prop.properties.get(6); |
||||||
|
userDataValue = new Vector3f((float) x, (float) y, (float) z); |
||||||
|
} else { |
||||||
|
scene.warning("Unsupported user data type: " + userDataType + ". Ignoring."); |
||||||
|
continue; |
||||||
|
} |
||||||
|
node.setUserData(userDataKey, userDataValue); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
FbxElement cullingElement = element.getChildById("Culling"); |
||||||
|
if(cullingElement != null && cullingElement.properties.get(0).equals("CullingOff")) |
||||||
|
cullMode = FaceCullMode.Off; // TODO Add other variants
|
||||||
|
|
||||||
|
/*From http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/the-makeup-of-the-local-matrix-of-an-kfbxnode/
|
||||||
|
|
||||||
|
Local Matrix = LclTranslation * RotationOffset * RotationPivot * |
||||||
|
PreRotation * LclRotation * PostRotation * RotationPivotInverse * |
||||||
|
ScalingOffset * ScalingPivot * LclScaling * ScalingPivotInverse |
||||||
|
|
||||||
|
LocalTranslation : translate (xform -query -translation) |
||||||
|
RotationOffset: translation compensates for the change in the rotate pivot point (xform -q -rotateTranslation) |
||||||
|
RotationPivot: current rotate pivot position (xform -q -rotatePivot) |
||||||
|
PreRotation : joint orientation(pre rotation) |
||||||
|
LocalRotation: rotate transform (xform -q -rotation & xform -q -rotateOrder) |
||||||
|
PostRotation : rotate axis (xform -q -rotateAxis) |
||||||
|
RotationPivotInverse: inverse of RotationPivot |
||||||
|
ScalingOffset: translation compensates for the change in the scale pivot point (xform -q -scaleTranslation) |
||||||
|
ScalingPivot: current scale pivot position (xform -q -scalePivot) |
||||||
|
LocalScaling: scale transform (xform -q -scale) |
||||||
|
ScalingPivotInverse: inverse of ScalingPivot |
||||||
|
*/ |
||||||
|
|
||||||
|
RotationOrder rotOrder = rotationActive ? rotationOrder : RotationOrder.EULER_XYZ; |
||||||
|
|
||||||
|
Matrix4f transformMatrix = new Matrix4f(); |
||||||
|
transformMatrix.setTranslation(translationLocalRaw.x + rotationOffsetRaw.x + rotationPivotRaw.x, translationLocalRaw.y + rotationOffsetRaw.y + rotationPivotRaw.y, translationLocalRaw.z + rotationOffsetRaw.z + rotationPivotRaw.z); |
||||||
|
|
||||||
|
if(rotationActive) { |
||||||
|
Quaternion postRotation = rotOrder.rotate(rotationPostRaw.x, rotationPostRaw.y, rotationPostRaw.z); |
||||||
|
Quaternion localRotation = rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z); |
||||||
|
Quaternion preRotation = rotOrder.rotate(rotationPreRaw.x, rotationPreRaw.y, rotationPreRaw.z); |
||||||
|
//preRotation.multLocal(localRotation).multLocal(postRotation);
|
||||||
|
postRotation.multLocal(localRotation).multLocal(preRotation); |
||||||
|
transformMatrix.multLocal(postRotation); |
||||||
|
} else { |
||||||
|
transformMatrix.multLocal(rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z)); |
||||||
|
} |
||||||
|
|
||||||
|
Matrix4f mat = new Matrix4f(); |
||||||
|
mat.setTranslation(scaleOffsetRaw.x + scalePivotRaw.x - rotationPivotRaw.x, scaleOffsetRaw.y + scalePivotRaw.y - rotationPivotRaw.y, scaleOffsetRaw.z + scalePivotRaw.z - rotationPivotRaw.z); |
||||||
|
transformMatrix.multLocal(mat); |
||||||
|
|
||||||
|
transformMatrix.scale(scaleLocalRaw); |
||||||
|
transformMatrix.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize)); |
||||||
|
|
||||||
|
mat.setTranslation(scalePivotRaw.negate()); |
||||||
|
transformMatrix.multLocal(mat); |
||||||
|
|
||||||
|
localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector()); |
||||||
|
|
||||||
|
node.setLocalTransform(localTransform); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void linkToZero() { |
||||||
|
scene.sceneNode.attachChild(node); |
||||||
|
} |
||||||
|
|
||||||
|
public void setSkeleton(Skeleton skeleton) { |
||||||
|
if(bone != null) |
||||||
|
boneIndex = skeleton.getBoneIndex(bone); |
||||||
|
} |
||||||
|
|
||||||
|
public void buildBindPoseBoneTransform() { |
||||||
|
if(bone != null) { |
||||||
|
Matrix4f t = bindTransform; |
||||||
|
if(t != null) { |
||||||
|
Matrix4f parentMatrix = parentFbxNode != null ? parentFbxNode.bindTransform : Matrix4f.IDENTITY; |
||||||
|
if(parentMatrix == null) |
||||||
|
parentMatrix = node.getLocalToWorldMatrix(null); |
||||||
|
t = parentMatrix.invert().multLocal(t); |
||||||
|
bone.setBindTransforms(t.toTranslationVector(), t.toRotationQuat(), t.toScaleVector()); |
||||||
|
} else { |
||||||
|
bone.setBindTransforms(node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void link(FbxObject child, String propertyName) { |
||||||
|
if(child instanceof FbxAnimNode) { |
||||||
|
FbxAnimNode anim = (FbxAnimNode) child; |
||||||
|
switch(propertyName) { |
||||||
|
case "Lcl Translation": |
||||||
|
animTranslations.put(anim.layerId, anim); |
||||||
|
lastAnimTranslation = anim; |
||||||
|
break; |
||||||
|
case "Lcl Rotation": |
||||||
|
animRotations.put(anim.layerId, anim); |
||||||
|
lastAnimRotation = anim; |
||||||
|
break; |
||||||
|
case "Lcl Scaling": |
||||||
|
animScales.put(anim.layerId, anim); |
||||||
|
lastAnimScale = anim; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public FbxAnimNode animTranslation(long layerId) { |
||||||
|
if(layerId == 0) |
||||||
|
return lastAnimTranslation; |
||||||
|
return animTranslations.get(layerId); |
||||||
|
} |
||||||
|
|
||||||
|
public FbxAnimNode animRotation(long layerId) { |
||||||
|
if(layerId == 0) |
||||||
|
return lastAnimRotation; |
||||||
|
return animRotations.get(layerId); |
||||||
|
} |
||||||
|
|
||||||
|
public FbxAnimNode animScale(long layerId) { |
||||||
|
if(layerId == 0) |
||||||
|
return lastAnimScale; |
||||||
|
return animScales.get(layerId); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void link(FbxObject otherObject) { |
||||||
|
if(otherObject instanceof FbxMaterial) { |
||||||
|
FbxMaterial m = (FbxMaterial) otherObject; |
||||||
|
Material mat = m.material; |
||||||
|
if(cullMode != FaceCullMode.Back) |
||||||
|
mat.getAdditionalRenderState().setFaceCullMode(cullMode); |
||||||
|
for(Geometry g : mesh.geometries) { |
||||||
|
if(g.getUserData("FBXMaterial") != null) { |
||||||
|
if((Integer) g.getUserData("FBXMaterial") == mesh.lastMaterialId) |
||||||
|
g.setMaterial(mat); |
||||||
|
} else { |
||||||
|
g.setMaterial(mat); |
||||||
|
} |
||||||
|
} |
||||||
|
mesh.lastMaterialId++; |
||||||
|
} else if(otherObject instanceof FbxNode) { |
||||||
|
FbxNode n = (FbxNode) otherObject; |
||||||
|
node.attachChild(n.node); |
||||||
|
n.parentFbxNode = this; |
||||||
|
if(isLimb() && n.isLimb()) { |
||||||
|
if(bone == null) |
||||||
|
bone = new Bone(name); |
||||||
|
if(n.bone == null) |
||||||
|
n.bone = new Bone(n.name); |
||||||
|
bone.addChild(n.bone); |
||||||
|
} |
||||||
|
} else if(otherObject instanceof FbxMesh) { |
||||||
|
FbxMesh m = (FbxMesh) otherObject; |
||||||
|
m.setParent(node); |
||||||
|
m.parent = this; |
||||||
|
mesh = m; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isLimb() { |
||||||
|
return type.equals("LimbNode"); |
||||||
|
} |
||||||
|
} |
@ -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