FBX: new FBX importer (not yet enabled - old importer still used by default)

Still needs work:
 * Skeletal animation (many issues with transform hierarchies)
 * N-gons triangulation (only quads supported at the moment)
 * Light & Camera importing
 * Z-up to Y-up correction
 * Morph animation
experimental
Kirill Vainer 10 years ago
parent 6f29772862
commit ed2be5e542
  1. 413
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java
  2. 144
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java
  3. 147
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java
  4. 82
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java
  5. 111
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java
  6. 44
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java
  7. 103
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java
  8. 98
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java
  9. 94
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java
  10. 66
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java
  11. 202
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java
  12. 190
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java
  13. 363
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java
  14. 234
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java
  15. 146
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java
  16. 107
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java
  17. 243
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java
  18. 316
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java
  19. 69
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java
  20. 59
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java
  21. 145
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java
  22. 617
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java
  23. 41
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java
  24. 61
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java
  25. 59
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java
  26. 45
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java
  27. 144
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java
  28. 209
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java
  29. 54
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java
  30. 89
      jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java
  31. 46
      jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java
  32. 46
      jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java
  33. 400
      jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java
  34. 170
      jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java

@ -0,0 +1,413 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx;
import com.jme3.animation.AnimControl;
import com.jme3.animation.Animation;
import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.animation.Track;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.AssetManager;
import com.jme3.asset.ModelKey;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.fbx.anim.FbxToJmeTrack;
import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode;
import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer;
import com.jme3.scene.plugins.fbx.anim.FbxAnimStack;
import com.jme3.scene.plugins.fbx.anim.FbxBindPose;
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode;
import com.jme3.scene.plugins.fbx.file.FbxDump;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.file.FbxFile;
import com.jme3.scene.plugins.fbx.file.FbxReader;
import com.jme3.scene.plugins.fbx.file.FbxId;
import com.jme3.scene.plugins.fbx.misc.FbxGlobalSettings;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import com.jme3.scene.plugins.fbx.node.FbxRootNode;
import com.jme3.scene.plugins.fbx.obj.FbxObjectFactory;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxLoader implements AssetLoader {
private static final Logger logger = Logger.getLogger(FbxLoader.class.getName());
private AssetManager assetManager;
private String sceneName;
private String sceneFilename;
private String sceneFolderName;
private FbxGlobalSettings globalSettings;
private final Map<FbxId, FbxObject> objectMap = new HashMap<FbxId, FbxObject>();
private final List<FbxAnimStack> animStacks = new ArrayList<FbxAnimStack>();
private final List<FbxBindPose> bindPoses = new ArrayList<FbxBindPose>();
@Override
public Object load(AssetInfo assetInfo) throws IOException {
this.assetManager = assetInfo.getManager();
AssetKey<?> assetKey = assetInfo.getKey();
if (!(assetKey instanceof ModelKey)) {
throw new AssetLoadException("Invalid asset key");
}
InputStream stream = assetInfo.openStream();
try {
sceneFilename = assetKey.getName();
sceneFolderName = assetKey.getFolder();
String ext = assetKey.getExtension();
sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1);
if (sceneFolderName != null && sceneFolderName.length() > 0) {
sceneName = sceneName.substring(sceneFolderName.length());
}
reset();
// Load the data from the stream.
loadData(stream);
// Bind poses are needed to compute world transforms.
applyBindPoses();
// Need world transforms for skeleton creation.
updateWorldTransforms();
// Need skeletons for meshs to be created in scene graph construction.
// Mesh bone indices require skeletons to determine bone index.
constructSkeletons();
// Create the jME3 scene graph from the FBX scene graph.
// Also creates SkeletonControls based on the constructed skeletons.
Spatial scene = constructSceneGraph();
// Load animations into AnimControls
constructAnimations();
return scene;
} finally {
releaseObjects();
if (stream != null) {
stream.close();
}
}
}
private void reset() {
globalSettings = new FbxGlobalSettings();
}
private void releaseObjects() {
globalSettings = null;
objectMap.clear();
animStacks.clear();
}
private void loadData(InputStream stream) throws IOException {
FbxFile scene = FbxReader.readFBX(stream);
FbxDump.dumpFile(scene);
// TODO: Load FBX object templates
for (FbxElement e : scene.rootElements) {
if (e.id.equals("FBXHeaderExtension")) {
loadHeader(e);
} else if (e.id.equals("GlobalSettings")) {
loadGlobalSettings(e);
} else if (e.id.equals("Objects")) {
loadObjects(e);
} else if (e.id.equals("Connections")) {
connectObjects(e);
}
}
}
private void loadHeader(FbxElement element) {
for (FbxElement e : element.children) {
if (e.id.equals("FBXVersion")) {
Integer version = (Integer) e.properties.get(0);
if (version < 7100) {
logger.log(Level.WARNING, "FBX file version is older than 7.1. "
+ "Some features may not work.");
}
}
}
}
private void loadGlobalSettings(FbxElement element) {
globalSettings = new FbxGlobalSettings();
globalSettings.fromElement(element);
}
private void loadObjects(FbxElement element) {
// Initialize the FBX root element.
objectMap.put(FbxId.ROOT, new FbxRootNode(assetManager, sceneFolderName));
for(FbxElement e : element.children) {
if (e.id.equals("GlobalSettings")) {
// Old FBX files seem to have the GlobalSettings element
// under Objects (??)
globalSettings.fromElement(e);
} else {
FbxObject object = FbxObjectFactory.createObject(e, assetManager, sceneFolderName);
if (object != null) {
if (objectMap.containsKey(object.getId())) {
logger.log(Level.WARNING, "An object with ID \"{0}\" has "
+ "already been defined. "
+ "Ignoring.",
object.getId());
}
objectMap.put(object.getId(), object);
if (object instanceof FbxAnimStack) {
// NOTE: animation stacks are implicitly global.
// Capture them here.
animStacks.add((FbxAnimStack) object);
} else if (object instanceof FbxBindPose) {
bindPoses.add((FbxBindPose) object);
}
} else {
throw new UnsupportedOperationException("Failed to create FBX object of type: " + e.id);
}
}
}
}
private void removeUnconnectedObjects() {
for (FbxObject object : new ArrayList<FbxObject>(objectMap.values())) {
if (!object.isJmeObjectCreated()) {
logger.log(Level.WARNING, "Purging orphan FBX object: {0}", object);
objectMap.remove(object.getId());
}
}
}
private void connectObjects(FbxElement element) {
if (objectMap.isEmpty()) {
logger.log(Level.WARNING, "FBX file is missing object information");
return;
} else if (objectMap.size() == 1) {
// Only root node (automatically added by jME3)
logger.log(Level.WARNING, "FBX file has no objects");
return;
}
for (FbxElement el : element.children) {
if (!el.id.equals("C") && !el.id.equals("Connect")) {
continue;
}
String type = (String) el.properties.get(0);
FbxId childId;
FbxId parentId;
if (type.equals("OO")) {
childId = FbxId.create(el.properties.get(1));
parentId = FbxId.create(el.properties.get(2));
FbxObject child = objectMap.get(childId);
FbxObject parent;
if (parentId.isNull()) {
// TODO: maybe clean this up a bit..
parent = objectMap.get(FbxId.ROOT);
} else {
parent = objectMap.get(parentId);
}
if (parent == null) {
throw new UnsupportedOperationException("Cannot find parent object ID \"" + parentId + "\"");
}
parent.connectObject(child);
} else if (type.equals("OP")) {
childId = FbxId.create(el.properties.get(1));
parentId = FbxId.create(el.properties.get(2));
String propName = (String) el.properties.get(3);
FbxObject child = objectMap.get(childId);
FbxObject parent = objectMap.get(parentId);
parent.connectObjectProperty(child, propName);
} else {
logger.log(Level.WARNING, "Unknown connection type: {0}. Ignoring.", type);
}
}
}
/**
* Copies the bind poses from FBX BindPose objects to FBX nodes.
* Must be called prior to {@link #updateWorldTransforms()}.
*/
private void applyBindPoses() {
for (FbxBindPose bindPose : bindPoses) {
Map<FbxId, Matrix4f> bindPoseData = bindPose.getJmeObject();
logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size());
for (Map.Entry<FbxId, Matrix4f> entry : bindPoseData.entrySet()) {
FbxObject obj = objectMap.get(entry.getKey());
if (obj instanceof FbxNode) {
FbxNode node = (FbxNode) obj;
node.setWorldBindPose(entry.getValue());
} else {
logger.log(Level.WARNING, "Bind pose can only be applied to FBX nodes. Ignoring.");
}
}
}
}
/**
* Updates world transforms and bind poses for the FBX scene graph.
*/
private void updateWorldTransforms() {
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
fbxRoot.updateWorldTransforms(null, null);
}
private void constructAnimations() {
// In FBX, animation are not attached to any root.
// They are implicitly global.
// So, we need to use hueristics to find which node(s)
// an animation is associated with, so we can create the AnimControl
// in the appropriate location in the scene.
Map<FbxToJmeTrack, FbxToJmeTrack> pairs = new HashMap<FbxToJmeTrack, FbxToJmeTrack>();
for (FbxAnimStack stack : animStacks) {
for (FbxAnimLayer layer : stack.getLayers()) {
for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) {
for (Map.Entry<FbxNode, String> nodePropertyEntry : curveNode.getInfluencedNodeProperties().entrySet()) {
FbxToJmeTrack lookupPair = new FbxToJmeTrack();
lookupPair.animStack = stack;
lookupPair.animLayer = layer;
lookupPair.node = nodePropertyEntry.getKey();
// Find if this pair is already stored
FbxToJmeTrack storedPair = pairs.get(lookupPair);
if (storedPair == null) {
// If not, store it.
storedPair = lookupPair;
pairs.put(storedPair, storedPair);
}
String property = nodePropertyEntry.getValue();
storedPair.animCurves.put(property, curveNode);
}
}
}
}
// At this point we can construct the animation for all pairs ...
for (FbxToJmeTrack pair : pairs.values()) {
String animName = pair.animStack.getName();
float duration = pair.animStack.getDuration();
System.out.println("ANIMATION: " + animName + ", duration = " + duration);
System.out.println("NODE: " + pair.node.getName());
duration = pair.getDuration();
if (pair.node instanceof FbxLimbNode) {
// Find the spatial that has the skeleton for this limb.
FbxLimbNode limbNode = (FbxLimbNode) pair.node;
Bone bone = limbNode.getJmeBone();
Spatial jmeSpatial = limbNode.getSkeletonHolder().getJmeObject();
Skeleton skeleton = limbNode.getSkeletonHolder().getJmeSkeleton();
// Get the animation control (create if missing).
AnimControl animControl = jmeSpatial.getControl(AnimControl.class);
if (animControl.getSkeleton() != skeleton) {
throw new UnsupportedOperationException();
}
// Get the animation (create if missing).
Animation anim = animControl.getAnim(animName);
if (anim == null) {
anim = new Animation(animName, duration);
animControl.addAnim(anim);
}
// Find the bone index from the spatial's skeleton.
int boneIndex = skeleton.getBoneIndex(bone);
// Generate the bone track.
BoneTrack bt = pair.toJmeBoneTrack(boneIndex, bone.getBindInverseTransform());
// Add the bone track to the animation.
anim.addTrack(bt);
} else {
// Create the spatial animation
Animation anim = new Animation(animName, duration);
anim.setTracks(new Track[]{ pair.toJmeSpatialTrack() });
// Get the animation control (create if missing).
Spatial jmeSpatial = pair.node.getJmeObject();
AnimControl animControl = jmeSpatial.getControl(AnimControl.class);
if (animControl == null) {
animControl = new AnimControl(null);
jmeSpatial.addControl(animControl);
}
// Add the spatial animation
animControl.addAnim(anim);
}
}
}
private void constructSkeletons() {
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
FbxNode.createSkeletons(fbxRoot);
}
private Spatial constructSceneGraph() {
// Acquire the implicit root object.
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
// Convert it into a jME3 scene
Node jmeRoot = (Node) FbxNode.createScene(fbxRoot);
// Fix the name (will probably be set to something like "-node")
jmeRoot.setName(sceneName + "-scene");
return jmeRoot;
}
}

@ -0,0 +1,144 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.math.FastMath;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
public class FbxAnimCurve extends FbxObject {
private long[] keyTimes;
private float[] keyValues;
public FbxAnimCurve(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement e : element.children) {
if (e.id.equals("KeyTime")) {
keyTimes = (long[]) e.properties.get(0);
} else if (e.id.equals("KeyValueFloat")) {
keyValues = (float[]) e.properties.get(0);
}
}
long time = -1;
for (int i = 0; i < keyTimes.length; i++) {
if (time >= keyTimes[i]) {
throw new UnsupportedOperationException("Keyframe times must be sequential, but they are not.");
}
time = keyTimes[i];
}
}
/**
* Get the times for the keyframes.
* @return Keyframe times.
*/
public long[] getKeyTimes() {
return keyTimes;
}
/**
* Retrieve the curve value at the given time.
* If the curve has no data, 0 is returned.
* If the time is outside the curve, then the closest value is returned.
* If the time isn't on an exact keyframe, linear interpolation is used
* to determine the value between the keyframes at the given time.
* @param time The time to get the curve value at (in FBX time units).
* @return The value at the given time.
*/
public float getValueAtTime(long time) {
if (keyTimes.length == 0) {
return 0;
}
// If the time is outside the range,
// we just return the closest value. (No extrapolation)
if (time <= keyTimes[0]) {
return keyValues[0];
} else if (time >= keyTimes[keyTimes.length - 1]) {
return keyValues[keyValues.length - 1];
}
int startFrame = 0;
int endFrame = 1;
int lastFrame = keyTimes.length - 1;
for (int i = 0; i < lastFrame && keyTimes[i] < time; ++i) {
startFrame = i;
endFrame = i + 1;
}
long keyTime1 = keyTimes[startFrame];
float keyValue1 = keyValues[startFrame];
long keyTime2 = keyTimes[endFrame];
float keyValue2 = keyValues[endFrame];
if (keyTime2 == time) {
return keyValue2;
}
long prevToNextDelta = keyTime2 - keyTime1;
long prevToCurrentDelta = time - keyTime1;
float lerpAmount = (float)prevToCurrentDelta / prevToNextDelta;
return FastMath.interpolateLinear(lerpAmount, keyValue1, keyValue2);
}
@Override
protected Object toJmeObject() {
// An AnimCurve has no jME3 representation.
// The parent AnimCurveNode is responsible to create the jME3
// representation.
throw new UnsupportedOperationException("No jME3 object conversion available");
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,147 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxAnimCurveNode extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxAnimCurveNode.class.getName());
private final Map<FbxNode, String> influencedNodePropertiesMap = new HashMap<FbxNode, String>();
private final Map<String, FbxAnimCurve> propertyToCurveMap = new HashMap<String, FbxAnimCurve>();
private final Map<String, Float> propertyToDefaultMap = new HashMap<String, Float>();
public FbxAnimCurveNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement prop : element.getFbxProperties()) {
String propName = (String) prop.properties.get(0);
String propType = (String) prop.properties.get(1);
if (propType.equals("Number")) {
float propValue = ((Double) prop.properties.get(4)).floatValue();
propertyToDefaultMap.put(propName, propValue);
}
}
}
public void addInfluencedNode(FbxNode node, String property) {
influencedNodePropertiesMap.put(node, property);
}
public Map<FbxNode, String> getInfluencedNodeProperties() {
return influencedNodePropertiesMap;
}
public Collection<FbxAnimCurve> getCurves() {
return propertyToCurveMap.values();
}
public Vector3f getVector3Value(long time) {
Vector3f value = new Vector3f();
FbxAnimCurve xCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X);
FbxAnimCurve yCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y);
FbxAnimCurve zCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z);
Float xDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X);
Float yDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y);
Float zDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z);
value.x = xCurve != null ? xCurve.getValueAtTime(time) : xDefault;
value.y = yCurve != null ? yCurve.getValueAtTime(time) : yDefault;
value.z = zCurve != null ? zCurve.getValueAtTime(time) : zDefault;
return value;
}
/**
* Converts the euler angles from {@link #getVector3Value(long)} to
* a quaternion rotation.
* @param time Time at which to get the euler angles.
* @return The rotation at time
*/
public Quaternion getQuaternionValue(long time) {
Vector3f eulerAngles = getVector3Value(time);
System.out.println("\tT: " + time + ". Rotation: " +
eulerAngles.x + ", " +
eulerAngles.y + ", " + eulerAngles.z);
Quaternion q = new Quaternion();
q.fromAngles(eulerAngles.x * FastMath.DEG_TO_RAD,
eulerAngles.y * FastMath.DEG_TO_RAD,
eulerAngles.z * FastMath.DEG_TO_RAD);
return q;
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
if (!(object instanceof FbxAnimCurve)) {
unsupportedConnectObjectProperty(object, property);
}
if (!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_X) &&
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Y) &&
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Z) &&
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_VISIBILITY)) {
logger.log(Level.WARNING, "Animating the dimension ''{0}'' is not "
+ "supported yet. Ignoring.", property);
return;
}
if (propertyToCurveMap.containsKey(property)) {
throw new UnsupportedOperationException("!");
}
propertyToCurveMap.put(property, (FbxAnimCurve) object);
}
}

@ -0,0 +1,82 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
public class FbxAnimLayer extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxAnimLayer.class.getName());
private final List<FbxAnimCurveNode> animCurves = new ArrayList<FbxAnimCurveNode>();
public FbxAnimLayer(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
// No known properties for layers..
// Also jME3 doesn't support multiple layers anyway.
}
public List<FbxAnimCurveNode> getAnimationCurveNodes() {
return Collections.unmodifiableList(animCurves);
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
if (!(object instanceof FbxAnimCurveNode)) {
unsupportedConnectObject(object);
}
animCurves.add((FbxAnimCurveNode) object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,111 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxAnimStack extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxAnimStack.class.getName());
private float duration;
private FbxAnimLayer layer0;
public FbxAnimStack(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement child : element.getFbxProperties()) {
String propName = (String) child.properties.get(0);
if (propName.equals("LocalStop")) {
long durationLong = (Long)child.properties.get(4);
duration = (float) (durationLong * FbxAnimUtil.SECONDS_PER_UNIT);
}
}
}
// /**
// * Finds out which FBX nodes this animation is going to influence.
// *
// * @return A list of FBX nodes that the stack's curves are influencing.
// */
// public Set<FbxNode> getInfluencedNodes() {
// HashSet<FbxNode> influencedNodes = new HashSet<FbxNode>();
// if (layer0 == null) {
// return influencedNodes;
// }
// for (FbxAnimCurveNode curveNode : layer0.getAnimationCurveNodes()) {
// influencedNodes.addAll(curveNode.getInfluencedNodes());
// }
// return influencedNodes;
// }
public float getDuration() {
return duration;
}
public FbxAnimLayer[] getLayers() {
return new FbxAnimLayer[]{ layer0 };
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
if (!(object instanceof FbxAnimLayer)) {
unsupportedConnectObject(object);
}
if (layer0 != null) {
logger.log(Level.WARNING, "jME3 does not support layered animation. "
+ "Only first layer has been loaded.");
return;
}
layer0 = (FbxAnimLayer) object;
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,44 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
public class FbxAnimUtil {
/**
* Conversion factor from FBX animation time unit to seconds.
*/
public static final double SECONDS_PER_UNIT = 1 / 46186158000d;
public static final String CURVE_NODE_PROPERTY_X = "d|X";
public static final String CURVE_NODE_PROPERTY_Y = "d|Y";
public static final String CURVE_NODE_PROPERTY_Z = "d|Z";
public static final String CURVE_NODE_PROPERTY_VISIBILITY = "d|Visibility";
}

@ -0,0 +1,103 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.math.Matrix4f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.file.FbxId;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.HashMap;
import java.util.Map;
public class FbxBindPose extends FbxObject<Map<FbxId, Matrix4f>> {
private final Map<FbxId, Matrix4f> bindPose = new HashMap<FbxId, Matrix4f>();
public FbxBindPose(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement child : element.children) {
if (!child.id.equals("PoseNode")) {
continue;
}
FbxId node = null;
float[] matData = null;
for (FbxElement e : child.children) {
if (e.id.equals("Node")) {
node = FbxId.create(e.properties.get(0));
} else if (e.id.equals("Matrix")) {
double[] matDataDoubles = (double[]) e.properties.get(0);
if (matDataDoubles.length != 16) {
// corrupt
throw new UnsupportedOperationException("Bind pose matrix "
+ "must have 16 doubles, but it has "
+ matDataDoubles.length + ". Data is corrupt");
}
matData = new float[16];
for (int i = 0; i < matDataDoubles.length; i++) {
matData[i] = (float) matDataDoubles[i];
}
}
}
if (node != null && matData != null) {
Matrix4f matrix = new Matrix4f(matData);
bindPose.put(node, matrix);
}
}
}
@Override
protected Map<FbxId, Matrix4f> toJmeObject() {
return bindPose;
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,98 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxCluster extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxCluster.class.getName());
private int[] indexes;
private double[] weights;
private FbxLimbNode limb;
public FbxCluster(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement e : element.children) {
if (e.id.equals("Indexes")) {
indexes = (int[]) e.properties.get(0);
} else if (e.id.equals("Weights")) {
weights = (double[]) e.properties.get(0);
}
}
}
public int[] getVertexIndices() {
return indexes;
}
public double[] getWeights() {
return weights;
}
public FbxLimbNode getLimb() {
return limb;
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxLimbNode) {
if (limb != null) {
logger.log(Level.WARNING, "This cluster already has a limb attached. Ignoring.");
return;
}
limb = (FbxLimbNode) object;
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,94 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import java.util.ArrayList;
import java.util.List;
public class FbxLimbNode extends FbxNode {
protected FbxNode skeletonHolder;
protected Bone bone;
public FbxLimbNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
private static void createBones(FbxNode skeletonHolderNode, FbxLimbNode limb, List<Bone> bones) {
limb.skeletonHolder = skeletonHolderNode;
Bone parentBone = limb.getJmeBone();
bones.add(parentBone);
for (FbxNode child : limb.children) {
if (child instanceof FbxLimbNode) {
FbxLimbNode childLimb = (FbxLimbNode) child;
createBones(skeletonHolderNode, childLimb, bones);
parentBone.addChild(childLimb.getJmeBone());
}
}
}
public static Skeleton createSkeleton(FbxNode skeletonHolderNode) {
if (skeletonHolderNode instanceof FbxLimbNode) {
throw new UnsupportedOperationException("Limb nodes cannot be skeleton holders");
}
List<Bone> bones = new ArrayList<Bone>();
for (FbxNode child : skeletonHolderNode.getChildren()) {
if (child instanceof FbxLimbNode) {
createBones(skeletonHolderNode, (FbxLimbNode) child, bones);
}
}
return new Skeleton(bones.toArray(new Bone[0]));
}
public FbxNode getSkeletonHolder() {
return skeletonHolder;
}
public Bone getJmeBone() {
if (bone == null) {
bone = new Bone(name);
bone.setBindTransforms(jmeLocalBindPose.getTranslation(),
jmeLocalBindPose.getRotation(),
jmeLocalBindPose.getScale());
}
return bone;
}
}

@ -0,0 +1,66 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import java.util.ArrayList;
import java.util.List;
public class FbxSkinDeformer extends FbxObject<List<FbxCluster>> {
private final List<FbxCluster> clusters = new ArrayList<FbxCluster>();
public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
protected List<FbxCluster> toJmeObject() {
return clusters;
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxCluster) {
clusters.add((FbxCluster) object);
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,202 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.anim;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.SpatialTrack;
import com.jme3.animation.Track;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Maps animation stacks to influenced nodes.
* Will be used later to create jME3 tracks.
*/
public final class FbxToJmeTrack {
public FbxAnimStack animStack;
public FbxAnimLayer animLayer;
public FbxNode node;
// These are not used in map lookups.
public transient final Map<String, FbxAnimCurveNode> animCurves = new HashMap<String, FbxAnimCurveNode>();
public long[] getKeyTimes() {
Set<Long> keyFrameTimesSet = new HashSet<Long>();
for (FbxAnimCurveNode curveNode : animCurves.values()) {
for (FbxAnimCurve curve : curveNode.getCurves()) {
for (long keyTime : curve.getKeyTimes()) {
keyFrameTimesSet.add(keyTime);
}
}
}
long[] keyFrameTimes = new long[keyFrameTimesSet.size()];
int i = 0;
for (Long keyFrameTime : keyFrameTimesSet) {
keyFrameTimes[i++] = keyFrameTime;
}
Arrays.sort(keyFrameTimes);
return keyFrameTimes;
}
/**
* Generate a {@link BoneTrack} from the animation data, for the given
* boneIndex.
*
* @param boneIndex The bone index for which track data is generated for.
* @param inverseBindPose Inverse bind pose of the bone (in world space).
* @return A BoneTrack containing the animation data, for the specified
* boneIndex.
*/
public BoneTrack toJmeBoneTrack(int boneIndex, Transform inverseBindPose) {
return (BoneTrack) toJmeTrackInternal(boneIndex, inverseBindPose);
}
public SpatialTrack toJmeSpatialTrack() {
return (SpatialTrack) toJmeTrackInternal(-1, null);
}
public float getDuration() {
long[] keyframes = getKeyTimes();
return (float) (keyframes[keyframes.length - 1] * FbxAnimUtil.SECONDS_PER_UNIT);
}
private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) {
Transform t = new Transform();
t.setTranslation(translation);
t.setRotation(rotation);
if (scale != null) {
t.setScale(scale);
}
t.combineWithParent(inverseBindPose);
t.getTranslation(translation);
t.getRotation(rotation);
if (scale != null) {
t.getScale(scale);
}
}
private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) {
float duration = animStack.getDuration();
FbxAnimCurveNode translationCurve = animCurves.get("Lcl Translation");
FbxAnimCurveNode rotationCurve = animCurves.get("Lcl Rotation");
FbxAnimCurveNode scalingCurve = animCurves.get("Lcl Scaling");
long[] fbxTimes = getKeyTimes();
float[] times = new float[fbxTimes.length];
// Translations / Rotations must be set on all tracks.
// (Required for jME3)
Vector3f[] translations = new Vector3f[fbxTimes.length];
Quaternion[] rotations = new Quaternion[fbxTimes.length];
Vector3f[] scales = null;
if (scalingCurve != null) {
scales = new Vector3f[fbxTimes.length];
}
for (int i = 0; i < fbxTimes.length; i++) {
long fbxTime = fbxTimes[i];
float time = (float) (fbxTime * FbxAnimUtil.SECONDS_PER_UNIT);
if (time > duration) {
// Expand animation duration to fit the curve.
duration = time;
System.out.println("actual duration: " + duration);
}
times[i] = time;
if (translationCurve != null) {
translations[i] = translationCurve.getVector3Value(fbxTime);
} else {
translations[i] = new Vector3f();
}
if (rotationCurve != null) {
rotations[i] = rotationCurve.getQuaternionValue(fbxTime);
if (i > 0) {
if (rotations[i - 1].dot(rotations[i]) < 0) {
System.out.println("rotation will go the long way, oh noes");
rotations[i - 1].negate();
}
}
} else {
rotations[i] = new Quaternion();
}
if (scalingCurve != null) {
scales[i] = scalingCurve.getVector3Value(fbxTime);
}
if (inverseBindPose != null) {
applyInverse(translations[i], rotations[i], scales != null ? scales[i] : null, inverseBindPose);
}
}
if (boneIndex == -1) {
return new SpatialTrack(times, translations, rotations, scales);
} else {
if (scales != null) {
return new BoneTrack(boneIndex, times, translations, rotations, scales);
} else {
return new BoneTrack(boneIndex, times, translations, rotations);
}
}
}
@Override
public int hashCode() {
int hash = 3;
hash = 79 * hash + this.animStack.hashCode();
hash = 79 * hash + this.animLayer.hashCode();
hash = 79 * hash + this.node.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
final FbxToJmeTrack other = (FbxToJmeTrack) obj;
return this.node == other.node
&& this.animStack == other.animStack
&& this.animLayer == other.animLayer;
}
}

@ -0,0 +1,190 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.material;
import com.jme3.asset.AssetLoadException;
import com.jme3.asset.AssetManager;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.asset.TextureKey;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.texture.Image;
import com.jme3.util.PlaceholderAssets;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class FbxImage extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxImage.class.getName());
protected TextureKey key;
protected String type; // = "Clip"
protected String filePath; // = "C:\Whatever\Blah\Texture.png"
protected String relativeFilePath; // = "..\Blah\Texture.png"
protected byte[] content; // = null, byte[0] OR embedded image data (unknown format?)
public FbxImage(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
if (element.propertiesTypes.length == 3) {
type = (String) element.properties.get(2);
} else {
type = (String) element.properties.get(1);
}
if (type.equals("Clip")) {
for (FbxElement e : element.children) {
if (e.id.equals("Type")) {
type = (String) e.properties.get(0);
} else if (e.id.equals("FileName")) {
filePath = (String) e.properties.get(0);
} else if (e.id.equals("RelativeFilename")) {
relativeFilePath = (String) e.properties.get(0);
} else if (e.id.equals("Content")) {
if (e.properties.size() > 0) {
byte[] storedContent = (byte[]) e.properties.get(0);
if (storedContent.length > 0) {
this.content = storedContent;
}
}
}
}
}
}
private Image loadImageSafe(AssetManager assetManager, TextureKey texKey) {
try {
return assetManager.loadTexture(texKey).getImage();
} catch (AssetNotFoundException ex) {
return null;
} catch (AssetLoadException ex) {
logger.log(Level.WARNING, "Error when loading image: " + texKey, ex);
return null;
}
}
private static String getFileName(String filePath) {
// NOTE: Gotta do it this way because new File().getParent()
// will not strip forward slashes on Linux / Mac OS X.
int fwdSlashIdx = filePath.lastIndexOf("\\");
int bkSlashIdx = filePath.lastIndexOf("/");
if (fwdSlashIdx != -1) {
filePath = filePath.substring(fwdSlashIdx + 1);
} else if (bkSlashIdx != -1) {
filePath = filePath.substring(bkSlashIdx + 1);
}
return filePath;
}
/**
* The texture key that was used to load the image.
* Only valid after {@link #getJmeObject()} has been called.
* @return the key that was used to load the image.
*/
public TextureKey getTextureKey() {
return key;
}
@Override
protected Object toJmeObject() {
Image image = null;
String fileName = null;
String relativeFilePathJme;
if (filePath != null) {
fileName = getFileName(filePath);
} else if (relativeFilePath != null) {
fileName = getFileName(relativeFilePath);
}
if (fileName != null) {
try {
// Try to load filename relative to FBX folder
key = new TextureKey(sceneFolderName + fileName);
key.setGenerateMips(true);
image = loadImageSafe(assetManager, key);
// Try to load relative filepath relative to FBX folder
if (image == null && relativeFilePath != null) {
// Convert Windows paths to jME3 paths
relativeFilePathJme = relativeFilePath.replace('\\', '/');
key = new TextureKey(sceneFolderName + relativeFilePathJme);
key.setGenerateMips(true);
image = loadImageSafe(assetManager, key);
}
// Try to load embedded image
if (image == null && content != null && content.length > 0) {
key = new TextureKey(fileName);
key.setGenerateMips(true);
InputStream is = new ByteArrayInputStream(content);
image = assetManager.loadAssetFromStream(key, is).getImage();
// NOTE: embedded texture doesn't exist in the asset manager,
// so the texture key must not be saved.
key = null;
}
} catch (AssetLoadException ex) {
logger.log(Level.WARNING, "Error while attempting to load texture {0}:\n{1}",
new Object[]{name, ex.toString()});
}
}
if (image == null) {
logger.log(Level.WARNING, "Cannot locate {0} for texture {1}", new Object[]{fileName, name});
image = PlaceholderAssets.getPlaceholderImage(assetManager);
}
// NOTE: At this point, key will be set to the last
// attempted texture key that we attempted to load.
return image;
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,363 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.material;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.texture.Texture;
import com.jme3.texture.image.ColorSpace;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxMaterial extends FbxObject<Material> {
private static final Logger logger = Logger.getLogger(FbxMaterial.class.getName());
private String shadingModel; // TODO: do we care about this? lambert just has no specular?
private final FbxMaterialProperties properties = new FbxMaterialProperties();
public FbxMaterial(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
if(!getSubclassName().equals("")) {
return;
}
FbxElement shadingModelEl = element.getChildById("ShadingModel");
if (shadingModelEl != null) {
shadingModel = (String) shadingModelEl.properties.get(0);
if (!shadingModel.equals("")) {
if (!shadingModel.equalsIgnoreCase("phong") &&
!shadingModel.equalsIgnoreCase("lambert")) {
logger.log(Level.WARNING, "FBX material uses unknown shading model: {0}. "
+ "Material may display incorrectly.", shadingModel);
}
}
}
for (FbxElement child : element.getFbxProperties()) {
properties.setPropertyFromElement(child);
}
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
if (!(object instanceof FbxTexture)) {
unsupportedConnectObjectProperty(object, property);
}
properties.setPropertyTexture(property, (FbxTexture) object);
}
private static void multRGB(ColorRGBA color, float factor) {
color.r *= factor;
color.g *= factor;
color.b *= factor;
}
@Override
protected Material toJmeObject() {
ColorRGBA ambient = null;
ColorRGBA diffuse = null;
ColorRGBA specular = null;
ColorRGBA transp = null;
ColorRGBA emissive = null;
float shininess = 1f;
boolean separateTexCoord = false;
Texture diffuseMap = null;
Texture specularMap = null;
Texture normalMap = null;
Texture transpMap = null;
Texture emitMap = null;
Texture aoMap = null;
FbxTexture fbxDiffuseMap = null;
Object diffuseColor = properties.getProperty("DiffuseColor");
if (diffuseColor != null) {
if (diffuseColor instanceof ColorRGBA) {
diffuse = ((ColorRGBA) diffuseColor).clone();
} else if (diffuseColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) diffuseColor;
fbxDiffuseMap = tex;
diffuseMap = tex.getJmeObject();
diffuseMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
Object diffuseFactor = properties.getProperty("DiffuseFactor");
if (diffuseFactor != null && diffuseFactor instanceof Float) {
float factor = (Float)diffuseFactor;
if (diffuse != null) {
multRGB(diffuse, factor);
} else {
diffuse = new ColorRGBA(factor, factor, factor, 1f);
}
}
Object specularColor = properties.getProperty("SpecularColor");
if (specularColor != null) {
if (specularColor instanceof ColorRGBA) {
specular = ((ColorRGBA) specularColor).clone();
} else if (specularColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) specularColor;
specularMap = tex.getJmeObject();
specularMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
Object specularFactor = properties.getProperty("SpecularFactor");
if (specularFactor != null && specularFactor instanceof Float) {
float factor = (Float)specularFactor;
if (specular != null) {
multRGB(specular, factor);
} else {
specular = new ColorRGBA(factor, factor, factor, 1f);
}
}
Object transparentColor = properties.getProperty("TransparentColor");
if (transparentColor != null) {
if (transparentColor instanceof ColorRGBA) {
transp = ((ColorRGBA) transparentColor).clone();
} else if (transparentColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) transparentColor;
transpMap = tex.getJmeObject();
transpMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
Object transparencyFactor = properties.getProperty("TransparencyFactor");
if (transparencyFactor != null && transparencyFactor instanceof Float) {
float factor = (Float)transparencyFactor;
if (transp != null) {
transp.a *= factor;
} else {
transp = new ColorRGBA(1f, 1f, 1f, factor);
}
}
Object emissiveColor = properties.getProperty("EmissiveColor");
if (emissiveColor != null) {
if (emissiveColor instanceof ColorRGBA) {
emissive = ((ColorRGBA)emissiveColor).clone();
} else if (emissiveColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) emissiveColor;
emitMap = tex.getJmeObject();
emitMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
Object emissiveFactor = properties.getProperty("EmissiveFactor");
if (emissiveFactor != null && emissiveFactor instanceof Float) {
float factor = (Float)emissiveFactor;
if (emissive != null) {
multRGB(emissive, factor);
} else {
emissive = new ColorRGBA(factor, factor, factor, 1f);
}
}
Object ambientColor = properties.getProperty("AmbientColor");
if (ambientColor != null && ambientColor instanceof ColorRGBA) {
ambient = ((ColorRGBA)ambientColor).clone();
}
Object ambientFactor = properties.getProperty("AmbientFactor");
if (ambientFactor != null && ambientFactor instanceof Float) {
float factor = (Float)ambientFactor;
if (ambient != null) {
multRGB(ambient, factor);
} else {
ambient = new ColorRGBA(factor, factor, factor, 1f);
}
}
Object shininessFactor = properties.getProperty("Shininess");
if (shininessFactor != null) {
if (shininessFactor instanceof Float) {
shininess = (Float) shininessFactor;
} else if (shininessFactor instanceof FbxTexture) {
// TODO: support shininess textures
}
}
Object bumpNormal = properties.getProperty("NormalMap");
if (bumpNormal != null) {
if (bumpNormal instanceof FbxTexture) {
// TODO: check all meshes that use this material have tangents
// otherwise shading errors occur
FbxTexture tex = (FbxTexture) bumpNormal;
normalMap = tex.getJmeObject();
normalMap.getImage().setColorSpace(ColorSpace.Linear);
}
}
Object aoColor = properties.getProperty("DiffuseColor2");
if (aoColor != null) {
if (aoColor instanceof FbxTexture) {
FbxTexture tex = (FbxTexture) aoColor;
if (tex.getUvSet() != null && fbxDiffuseMap != null) {
if (!tex.getUvSet().equals(fbxDiffuseMap.getUvSet())) {
separateTexCoord = true;
}
}
aoMap = tex.getJmeObject();
aoMap.getImage().setColorSpace(ColorSpace.sRGB);
}
}
// TODO: how to disable transparency from diffuse map?? Need "UseAlpha" again..
assert ambient == null || ambient.a == 1f;
assert diffuse == null || diffuse.a == 1f;
assert specular == null || specular.a == 1f;
assert emissive == null || emissive.a == 1f;
assert transp == null || (transp.r == 1f && transp.g == 1f && transp.b == 1f);
// If shininess is less than 1.0, the lighting shader won't be able
// to handle it. Gotta disable specularity then.
if (shininess < 1f) {
shininess = 1f;
specular = ColorRGBA.Black;
}
// Try to guess if we need to enable alpha blending.
// FBX does not specify this explicitly.
boolean useAlphaBlend = false;
if (diffuseMap != null && diffuseMap == transpMap) {
// jME3 already uses alpha from diffuseMap
// (if alpha blend is enabled)
useAlphaBlend = true;
transpMap = null;
} else if (diffuseMap != null && transpMap != null && diffuseMap != transpMap) {
// TODO: potential bug here. Alpha from diffuse may
// leak unintentionally.
useAlphaBlend = true;
} else if (transpMap != null) {
// We have alpha map but no diffuse map, OK.
useAlphaBlend = true;
}
if (transp != null && transp.a != 1f) {
// Consolidate transp into diffuse
// (jME3 doesn't use a separate alpha color)
// TODO: potential bug here. Alpha from diffuse may
// leak unintentionally.
useAlphaBlend = true;
if (diffuse != null) {
diffuse.a = transp.a;
} else {
diffuse = transp;
}
}
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setName(name);
// TODO: load this from FBX material.
mat.setReceivesShadows(true);
if (useAlphaBlend) {
// No idea if this is a transparent or translucent model, gotta guess..
mat.setTransparent(true);
mat.setFloat("AlphaDiscardThreshold", 0.01f);
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
}
mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
// Set colors.
if (ambient != null || diffuse != null || specular != null) {
// If either of those is set, we have to set them all.
// NOTE: default specular is black, unless it is set explicitly.
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Ambient", /*ambient != null ? ambient :*/ ColorRGBA.White);
mat.setColor("Diffuse", diffuse != null ? diffuse : ColorRGBA.White);
mat.setColor("Specular", specular != null ? specular : ColorRGBA.Black);
}
if (emissive != null) {
mat.setColor("GlowColor", emissive);
}
// Set shininess.
if (shininess > 1f) {
// Convert shininess from
// Phong (FBX shading model) to Blinn (jME3 shading model).
float blinnShininess = (shininess * 5.1f) + 1f;
mat.setFloat("Shininess", blinnShininess);
}
// Set textures.
if (diffuseMap != null) {
mat.setTexture("DiffuseMap", diffuseMap);
}
if (specularMap != null) {
mat.setTexture("SpecularMap", specularMap);
}
if (normalMap != null) {
mat.setTexture("NormalMap", normalMap);
}
if (transpMap != null) {
// mat.setTexture("AlphaMap", transpMap);
}
if (emitMap != null) {
mat.setTexture("GlowMap", emitMap);
}
if (aoMap != null) {
mat.setTexture("LightMap", aoMap);
if (separateTexCoord) {
mat.setBoolean("SeparateTexCoord", true);
}
}
return mat;
}
}

@ -0,0 +1,234 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxMaterialProperties {
private static final Logger logger = Logger.getLogger(FbxMaterialProperties.class.getName());
private static final Map<String, FBXMaterialProperty> propertyMetaMap = new HashMap<String, FBXMaterialProperty>();
private final Map<String, Object> propertyValueMap = new HashMap<String, Object>();
private static enum Type {
Color,
Alpha,
Factor,
Texture2DOrColor,
Texture2DOrAlpha,
Texture2DOrFactor,
Texture2D,
TextureCubeMap,
Ignore;
}
private static class FBXMaterialProperty {
private final String name;
private final Type type;
public FBXMaterialProperty(String name, Type type) {
this.name = name;
this.type = type;
}
}
private static boolean isValueAcceptable(Type type, Object value) {
if (type == Type.Ignore) {
return true;
}
if (value instanceof FbxTexture) {
switch (type) {
case Texture2D:
case Texture2DOrAlpha:
case Texture2DOrColor:
case Texture2DOrFactor:
return true;
}
} else if (value instanceof ColorRGBA) {
switch (type) {
case Color:
case Texture2DOrColor:
return true;
}
} else if (value instanceof Float) {
switch (type) {
case Alpha:
case Factor:
case Texture2DOrAlpha:
case Texture2DOrFactor:
return true;
}
}
return false;
}
private static void defineProp(String name, Type type) {
propertyMetaMap.put(name, new FBXMaterialProperty(name, type));
}
private static void defineAlias(String alias, String name) {
propertyMetaMap.put(alias, propertyMetaMap.get(name));
}
static {
// Lighting->Ambient
// TODO: Add support for AmbientMap??
defineProp("AmbientColor", Type.Color);
defineProp("AmbientFactor", Type.Factor);
defineAlias("Ambient", "AmbientColor");
// Lighting->DiffuseMap/Diffuse
defineProp("DiffuseColor", Type.Texture2DOrColor);
defineProp("DiffuseFactor", Type.Factor);
defineAlias("Diffuse", "DiffuseColor");
// Lighting->SpecularMap/Specular
defineProp("SpecularColor", Type.Texture2DOrColor);
defineProp("SpecularFactor", Type.Factor);
defineAlias("Specular", "SpecularColor");
// Lighting->AlphaMap/Diffuse
defineProp("TransparentColor", Type.Texture2DOrAlpha);
// Lighting->Diffuse
defineProp("TransparencyFactor", Type.Alpha);
defineAlias("Opacity", "TransparencyFactor");
// Lighting->GlowMap/GlowColor
defineProp("EmissiveColor", Type.Texture2DOrColor);
defineProp("EmissiveFactor", Type.Factor);
defineAlias("Emissive", "EmissiveColor");
// Lighting->Shininess
defineProp("Shininess", Type.Factor);
defineAlias("ShininessExponent", "Shininess");
// Lighting->NormalMap
defineProp("NormalMap", Type.Texture2D);
defineAlias("Normal", "NormalMap");
// Lighting->EnvMap
defineProp("ReflectionColor", Type.Texture2DOrColor);
// Lighting->FresnelParams
defineProp("Reflectivity", Type.Factor);
defineAlias("ReflectionFactor", "Reflectivity");
// ShadingModel is no longer specified under Properties element.
defineProp("ShadingModel", Type.Ignore);
// MultiLayer materials aren't supported anyway..
defineProp("MultiLayer", Type.Ignore);
// Not sure what this is.. NormalMap again??
defineProp("Bump", Type.Texture2DOrColor);
defineProp("BumpFactor", Type.Factor);
defineProp("DisplacementColor", Type.Color);
defineProp("DisplacementFactor", Type.Factor);
}
public void setPropertyTexture(String name, FbxTexture texture) {
FBXMaterialProperty prop = propertyMetaMap.get(name);
if (prop == null) {
logger.log(Level.WARNING, "Unknown FBX material property '{0}'", name);
return;
}
if (propertyValueMap.get(name) instanceof FbxTexture) {
// Multiple / layered textures ..
// Just write into 2nd slot for now (maybe will use for lightmaps).
name = name + "2";
}
propertyValueMap.put(name, texture);
}
public void setPropertyFromElement(FbxElement propertyElement) {
String name = (String) propertyElement.properties.get(0);
FBXMaterialProperty prop = propertyMetaMap.get(name);
if (prop == null) {
logger.log(Level.WARNING, "Unknown FBX material property ''{0}''", name);
return;
}
// It is either a color, alpha, or factor.
// Textures can only be set via setPropertyTexture.
// If it is an alias, get the real name of the property.
String realName = prop.name;
switch (prop.type) {
case Alpha:
case Factor:
case Texture2DOrFactor:
case Texture2DOrAlpha:
double value = (Double) propertyElement.properties.get(4);
propertyValueMap.put(realName, (float)value);
break;
case Color:
case Texture2DOrColor:
double x = (Double) propertyElement.properties.get(4);
double y = (Double) propertyElement.properties.get(5);
double z = (Double) propertyElement.properties.get(6);
ColorRGBA color = new ColorRGBA((float)x, (float)y, (float)z, 1f);
propertyValueMap.put(realName, color);
break;
default:
logger.log(Level.WARNING, "FBX material property ''{0}'' requires a texture.", name);
break;
}
}
public Object getProperty(String name) {
return propertyValueMap.get(name);
}
public static Type getPropertyType(String name) {
FBXMaterialProperty prop = propertyMetaMap.get(name);
if (prop == null) {
return null;
} else {
return prop.type;
}
}
}

@ -0,0 +1,146 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.material;
import com.jme3.asset.AssetManager;
import com.jme3.asset.TextureKey;
import com.jme3.math.Vector2f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapAxis;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
import com.jme3.util.PlaceholderAssets;
public class FbxTexture extends FbxObject<Texture> {
private static enum AlphaSource {
None,
FromTextureAlpha,
FromTextureIntensity;
}
private String type;
private FbxImage media;
// TODO: not currently used.
private AlphaSource alphaSource = AlphaSource.FromTextureAlpha;
private String uvSet;
private int wrapModeU = 0, wrapModeV = 0;
private final Vector2f uvTranslation = new Vector2f(0, 0);
private final Vector2f uvScaling = new Vector2f(1, 1);
public FbxTexture(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
public String getUvSet() {
return uvSet;
}
@Override
protected Texture toJmeObject() {
Image image = null;
TextureKey key = null;
if (media != null) {
image = (Image) media.getJmeObject();
key = media.getTextureKey();
}
if (image == null) {
image = PlaceholderAssets.getPlaceholderImage(assetManager);
}
Texture2D tex = new Texture2D(image);
if (key != null) {
tex.setKey(key);
tex.setName(key.getName());
tex.setAnisotropicFilter(key.getAnisotropy());
}
tex.setMinFilter(MinFilter.Trilinear);
tex.setMagFilter(MagFilter.Bilinear);
if (wrapModeU == 0) {
tex.setWrap(WrapAxis.S, WrapMode.Repeat);
}
if (wrapModeV == 0) {
tex.setWrap(WrapAxis.T, WrapMode.Repeat);
}
return tex;
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
if (getSubclassName().equals("")) {
for (FbxElement e : element.children) {
if (e.id.equals("Type")) {
type = (String) e.properties.get(0);
}
/*else if (e.id.equals("FileName")) {
filename = (String) e.properties.get(0);
}*/
}
for (FbxElement prop : element.getFbxProperties()) {
String propName = (String) prop.properties.get(0);
if (propName.equals("AlphaSource")) {
// ???
} else if (propName.equals("UVSet")) {
uvSet = (String) prop.properties.get(4);
} else if (propName.equals("WrapModeU")) {
wrapModeU = (Integer) prop.properties.get(4);
} else if (propName.equals("WrapModeV")) {
wrapModeV = (Integer) prop.properties.get(4);
}
}
}
}
@Override
public void connectObject(FbxObject object) {
if (!(object instanceof FbxImage)) {
unsupportedConnectObject(object);
// } else if (media != null) {
// throw new UnsupportedOperationException("An image is already attached to this texture.");
}
this.media = (FbxImage) object;
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,107 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.mesh;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import java.util.Collection;
import java.util.EnumMap;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class FbxLayer {
private static final Logger logger = Logger.getLogger(FbxLayer.class.getName());
public static class FbxLayerElementRef {
FbxLayerElement.Type layerElementType;
int layerElementIndex;
FbxLayerElement layerElement;
}
int layer;
final EnumMap<FbxLayerElement.Type, FbxLayerElementRef> references =
new EnumMap<FbxLayerElement.Type, FbxLayerElementRef>(FbxLayerElement.Type.class);
private FbxLayer() { }
public Object getVertexData(FbxLayerElement.Type type, int polygonIndex,
int polygonVertexIndex, int positionIndex, int edgeIndex) {
FbxLayerElementRef reference = references.get(type);
if (reference == null) {
return null;
} else {
return reference.layerElement.getVertexData(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex);
}
}
public FbxLayerElement.Type[] getLayerElementTypes() {
FbxLayerElement.Type[] types = new FbxLayerElement.Type[references.size()];
references.keySet().toArray(types);
return types;
}
public void setLayerElements(Collection<FbxLayerElement> layerElements) {
for (FbxLayerElement layerElement : layerElements) {
FbxLayerElementRef reference = references.get(layerElement.type);
if (reference != null && reference.layerElementIndex == layerElement.index) {
reference.layerElement = layerElement;
}
}
}
public static FbxLayer fromElement(FbxElement element) {
FbxLayer layer = new FbxLayer();
layer.layer = (Integer)element.properties.get(0);
next_element: for (FbxElement child : element.children) {
if (!child.id.equals("LayerElement")) {
continue;
}
FbxLayerElementRef ref = new FbxLayerElementRef();
for (FbxElement child2 : child.children) {
if (child2.id.equals("Type")) {
String layerElementTypeStr = (String) child2.properties.get(0);
layerElementTypeStr = layerElementTypeStr.substring("LayerElement".length());
try {
ref.layerElementType = FbxLayerElement.Type.valueOf(layerElementTypeStr);
} catch (IllegalArgumentException ex) {
logger.log(Level.WARNING, "Unsupported layer type: {0}. Ignoring.", layerElementTypeStr);
continue next_element;
}
} else if (child2.id.equals("TypedIndex")) {
ref.layerElementIndex = (Integer) child2.properties.get(0);
}
}
layer.references.put(ref.layerElementType, ref);
}
return layer;
}
}

@ -0,0 +1,243 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.mesh;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxLayerElement {
private static final Logger logger = Logger.getLogger(FbxLayerElement.class.getName());
public enum Type {
Position, // Vector3f (isn't actually defined in FBX)
BoneIndex, // List<Integer> (isn't actually defined in FBX)
BoneWeight, // List<Float> isn't actually defined in FBX)
Normal, // Vector3f
Binormal, // Vector3f
Tangent, // Vector3f
UV, // Vector2f
TransparentUV, // Vector2f
Color, // ColorRGBA
Material, // Integer
Smoothing, // Integer
Visibility, // Integer
Texture, // ??? (FBX 6.x)
PolygonGroup, // ??? (FBX 6.x)
NormalMapTextures, // ??? (FBX 6.x)
SpecularFactorUV, // ??? (FBX 6.x)
NormalMapUV, // ??? (FBX 6.x)
SpecularFactorTextures, // ??? (FBX 6.x)
}
public enum MappingInformationType {
NoMappingInformation,
AllSame,
ByPolygonVertex,
ByVertex,
ByPolygon,
ByEdge;
}
public enum ReferenceInformationType {
Direct,
IndexToDirect;
}
public enum TextureBlendMode {
Translucent;
}
private static final Set<String> indexTypes = new HashSet<String>();
static {
indexTypes.add("UVIndex");
indexTypes.add("NormalsIndex");
indexTypes.add("TangentsIndex");
indexTypes.add("BinormalsIndex");
indexTypes.add("Smoothing");
indexTypes.add("Materials");
indexTypes.add("TextureId");
indexTypes.add("ColorIndex");
indexTypes.add("PolygonGroup");
}
int index;
Type type;
ReferenceInformationType refInfoType;
MappingInformationType mapInfoType;
String name = "";
Object[] data;
int[] dataIndices;
private FbxLayerElement() { }
public String toString() {
return "LayerElement[type=" + type + ", layer=" + index +
", mapInfoType=" + mapInfoType + ", refInfoType=" + refInfoType + "]";
}
private Object getVertexDataIndexToDirect(int polygonIndex, int polygonVertexIndex,
int positionIndex, int edgeIndex) {
switch (mapInfoType) {
case AllSame: return data[dataIndices[0]];
case ByPolygon: return data[dataIndices[polygonIndex]];
case ByPolygonVertex: return data[dataIndices[polygonVertexIndex]];
case ByVertex: return data[dataIndices[positionIndex]];
case ByEdge: return data[dataIndices[edgeIndex]];
default: throw new UnsupportedOperationException();
}
}
private Object getVertexDataDirect(int polygonIndex, int polygonVertexIndex,
int positionIndex, int edgeIndex) {
switch (mapInfoType) {
case AllSame: return data[0];
case ByPolygon: return data[polygonIndex];
case ByPolygonVertex: return data[polygonVertexIndex];
case ByVertex: return data[positionIndex];
case ByEdge: return data[edgeIndex];
default: throw new UnsupportedOperationException();
}
}
public Object getVertexData(int polygonIndex, int polygonVertexIndex, int positionIndex, int edgeIndex) {
switch (refInfoType) {
case Direct: return getVertexDataDirect(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex);
case IndexToDirect: return getVertexDataIndexToDirect(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex);
default: return null;
}
}
public static FbxLayerElement fromPositions(double[] positionData) {
FbxLayerElement layerElement = new FbxLayerElement();
layerElement.index = -1;
layerElement.name = "";
layerElement.type = Type.Position;
layerElement.mapInfoType = MappingInformationType.ByVertex;
layerElement.refInfoType = ReferenceInformationType.Direct;
layerElement.data = toVector3(positionData);
layerElement.dataIndices = null;
return layerElement;
}
public static FbxLayerElement fromElement(FbxElement element) {
FbxLayerElement layerElement = new FbxLayerElement();
if (!element.id.startsWith("LayerElement")) {
throw new IllegalArgumentException("Not a layer element");
}
layerElement.index = (Integer)element.properties.get(0);
String elementType = element.id.substring("LayerElement".length());
try {
layerElement.type = Type.valueOf(elementType);
} catch (IllegalArgumentException ex) {
logger.log(Level.WARNING, "Unsupported layer element: {0}. Ignoring.", elementType);
}
for (FbxElement child : element.children) {
if (child.id.equals("MappingInformationType")) {
String mapInfoTypeVal = (String) child.properties.get(0);
if (mapInfoTypeVal.equals("ByVertice")) {
mapInfoTypeVal = "ByVertex";
}
layerElement.mapInfoType = MappingInformationType.valueOf(mapInfoTypeVal);
} else if (child.id.equals("ReferenceInformationType")) {
String refInfoTypeVal = (String) child.properties.get(0);
if (refInfoTypeVal.equals("Index")) {
refInfoTypeVal = "IndexToDirect";
}
layerElement.refInfoType = ReferenceInformationType.valueOf(refInfoTypeVal);
} else if (child.id.equals("Normals") || child.id.equals("Tangents") || child.id.equals("Binormals")) {
layerElement.data = toVector3(FbxMeshUtil.getDoubleArray(child));
} else if (child.id.equals("Colors")) {
layerElement.data = toColorRGBA(FbxMeshUtil.getDoubleArray(child));
} else if (child.id.equals("UV")) {
layerElement.data = toVector2(FbxMeshUtil.getDoubleArray(child));
} else if (indexTypes.contains(child.id)) {
layerElement.dataIndices = FbxMeshUtil.getIntArray(child);
} else if (child.id.equals("Name")) {
layerElement.name = (String) child.properties.get(0);
}
}
if (layerElement.data == null) {
// For Smoothing / Materials, data = dataIndices
layerElement.refInfoType = ReferenceInformationType.Direct;
layerElement.data = new Integer[layerElement.dataIndices.length];
for (int i = 0; i < layerElement.data.length; i++) {
layerElement.data[i] = layerElement.dataIndices[i];
}
layerElement.dataIndices = null;
}
return layerElement;
}
static Vector3f[] toVector3(double[] data) {
Vector3f[] vectors = new Vector3f[data.length / 3];
for (int i = 0; i < vectors.length; i++) {
float x = (float) data[i * 3];
float y = (float) data[i * 3 + 1];
float z = (float) data[i * 3 + 2];
vectors[i] = new Vector3f(x, y, z);
}
return vectors;
}
static Vector2f[] toVector2(double[] data) {
Vector2f[] vectors = new Vector2f[data.length / 2];
for (int i = 0; i < vectors.length; i++) {
float x = (float) data[i * 2];
float y = (float) data[i * 2 + 1];
vectors[i] = new Vector2f(x, y);
}
return vectors;
}
static ColorRGBA[] toColorRGBA(double[] data) {
ColorRGBA[] colors = new ColorRGBA[data.length / 4];
for (int i = 0; i < colors.length; i++) {
float r = (float) data[i * 4];
float g = (float) data[i * 4 + 1];
float b = (float) data[i * 4 + 2];
float a = (float) data[i * 4 + 3];
colors[i] = new ColorRGBA(r, g, b, a);
}
return colors;
}
}

@ -0,0 +1,316 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.mesh;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.plugins.IrUtils;
import com.jme3.scene.plugins.IrBoneWeightIndex;
import com.jme3.scene.plugins.IrMesh;
import com.jme3.scene.plugins.IrPolygon;
import com.jme3.scene.plugins.IrVertex;
import com.jme3.scene.plugins.fbx.anim.FbxCluster;
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode;
import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.scene.plugins.fbx.node.FbxNodeAttribute;
import com.jme3.util.IntMap;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
private static final Logger logger = Logger.getLogger(FbxMesh.class.getName());
private FbxPolygon[] polygons;
private int[] edges;
private FbxLayerElement[] layerElements;
private Vector3f[] positions;
private FbxLayer[] layers;
private ArrayList<Integer>[] boneIndices;
private ArrayList<Float>[] boneWeights;
private FbxSkinDeformer skinDeformer;
public FbxMesh(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
List<FbxLayerElement> layerElementsList = new ArrayList<FbxLayerElement>();
List<FbxLayer> layersList = new ArrayList<FbxLayer>();
for (FbxElement e : element.children) {
if (e.id.equals("Vertices")) {
setPositions(FbxMeshUtil.getDoubleArray(e));
} else if (e.id.equals("PolygonVertexIndex")) {
setPolygonVertexIndices(FbxMeshUtil.getIntArray(e));
} else if (e.id.equals("Edges")) {
setEdges(FbxMeshUtil.getIntArray(e));
} else if (e.id.startsWith("LayerElement")) {
layerElementsList.add(FbxLayerElement.fromElement(e));
} else if (e.id.equals("Layer")) {
layersList.add(FbxLayer.fromElement(e));
}
}
for (FbxLayer layer : layersList) {
layer.setLayerElements(layerElementsList);
}
layerElements = new FbxLayerElement[layerElementsList.size()];
layerElementsList.toArray(layerElements);
layers = new FbxLayer[layersList.size()];
layersList.toArray(layers);
}
public FbxSkinDeformer getSkinDeformer() {
return skinDeformer;
}
public void applyCluster(FbxCluster cluster) {
if (boneIndices == null) {
boneIndices = new ArrayList[positions.length];
boneWeights = new ArrayList[positions.length];
}
FbxLimbNode limb = cluster.getLimb();
Bone bone = limb.getJmeBone();
Skeleton skeleton = limb.getSkeletonHolder().getJmeSkeleton();
int boneIndex = skeleton.getBoneIndex(bone);
int[] positionIndices = cluster.getVertexIndices();
double[] weights = cluster.getWeights();
for (int i = 0; i < positionIndices.length; i++) {
int positionIndex = positionIndices[i];
float boneWeight = (float)weights[i];
ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex];
ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex];
if (boneIndicesForVertex == null) {
boneIndicesForVertex = new ArrayList<Integer>();
boneWeightsForVertex = new ArrayList<Float>();
boneIndices[positionIndex] = boneIndicesForVertex;
boneWeights[positionIndex] = boneWeightsForVertex;
}
boneIndicesForVertex.add(boneIndex);
boneWeightsForVertex.add(boneWeight);
}
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxSkinDeformer) {
if (skinDeformer != null) {
logger.log(Level.WARNING, "This mesh already has a skin deformer attached. Ignoring.");
return;
}
skinDeformer = (FbxSkinDeformer) object;
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
private void setPositions(double[] positions) {
this.positions = FbxLayerElement.toVector3(positions);
}
private void setEdges(int[] edges) {
this.edges = edges;
// TODO: ...
}
private void setPolygonVertexIndices(int[] polygonVertexIndices) {
List<FbxPolygon> polygonList = new ArrayList<FbxPolygon>();
boolean finishPolygon = false;
List<Integer> vertexIndices = new ArrayList<Integer>();
for (int i = 0; i < polygonVertexIndices.length; i++) {
int vertexIndex = polygonVertexIndices[i];
if (vertexIndex < 0) {
vertexIndex ^= -1;
finishPolygon = true;
}
vertexIndices.add(vertexIndex);
if (finishPolygon) {
finishPolygon = false;
polygonList.add(FbxPolygon.fromIndices(vertexIndices));
vertexIndices.clear();
}
}
polygons = new FbxPolygon[polygonList.size()];
polygonList.toArray(polygons);
}
private static IrBoneWeightIndex[] toBoneWeightIndices(List<Integer> boneIndices, List<Float> boneWeights) {
IrBoneWeightIndex[] boneWeightIndices = new IrBoneWeightIndex[boneIndices.size()];
for (int i = 0; i < boneIndices.size(); i++) {
boneWeightIndices[i] = new IrBoneWeightIndex(boneIndices.get(i), boneWeights.get(i));
}
return boneWeightIndices;
}
@Override
protected IntMap<Mesh> toJmeObject() {
// Load clusters from SkinDeformer
if (skinDeformer != null) {
for (FbxCluster cluster : skinDeformer.getJmeObject()) {
applyCluster(cluster);
}
}
IrMesh irMesh = toIRMesh();
// Trim bone weights to 4 weights per vertex.
IrUtils.trimBoneWeights(irMesh);
// Convert tangents / binormals to tangents with parity.
IrUtils.toTangentsWithParity(irMesh);
// Triangulate quads.
IrUtils.triangulate(irMesh);
// Split meshes by material indices.
IntMap<IrMesh> irMeshes = IrUtils.splitByMaterial(irMesh);
// Create a jME3 Mesh for each material index.
IntMap<Mesh> jmeMeshes = new IntMap<Mesh>();
for (IntMap.Entry<IrMesh> irMeshEntry : irMeshes) {
Mesh jmeMesh = IrUtils.convertIrMeshToJmeMesh(irMeshEntry.getValue());
jmeMeshes.put(irMeshEntry.getKey(), jmeMesh);
}
if (jmeMeshes.size() == 0) {
// When will this actually happen? Not sure.
logger.log(Level.WARNING, "Empty FBX mesh found (unusual).");
}
// IMPORTANT: If we have a -1 entry, those are triangles
// with no material indices.
// It makes sense only if the mesh uses a single material!
if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) {
logger.log(Level.WARNING, "Mesh has polygons with no material "
+ "indices (unusual) - they will use material index 0.");
}
return jmeMeshes;
}
/**
* Convert FBXMesh to IRMesh.
*/
public IrMesh toIRMesh() {
IrMesh newMesh = new IrMesh();
newMesh.polygons = new IrPolygon[polygons.length];
int polygonVertexIndex = 0;
int positionIndex = 0;
FbxLayer layer0 = layers[0];
FbxLayer layer1 = layers.length > 1 ? layers[1] : null;
for (int i = 0; i < polygons.length; i++) {
FbxPolygon polygon = polygons[i];
IrPolygon irPolygon = new IrPolygon();
irPolygon.vertices = new IrVertex[polygon.indices.length];
for (int j = 0; j < polygon.indices.length; j++) {
positionIndex = polygon.indices[j];
IrVertex irVertex = new IrVertex();
irVertex.pos = positions[positionIndex];
if (layer0 != null) {
irVertex.norm = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Normal, i, polygonVertexIndex, positionIndex, 0);
irVertex.tang = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Tangent, i, polygonVertexIndex, positionIndex, 0);
irVertex.bitang = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Binormal, i, polygonVertexIndex, positionIndex, 0);
irVertex.uv0 = (Vector2f) layer0.getVertexData(FbxLayerElement.Type.UV, i, polygonVertexIndex, positionIndex, 0);
irVertex.color = (ColorRGBA) layer0.getVertexData(FbxLayerElement.Type.Color, i, polygonVertexIndex, positionIndex, 0);
irVertex.material = (Integer) layer0.getVertexData(FbxLayerElement.Type.Material, i, polygonVertexIndex, positionIndex, 0);
irVertex.smoothing = (Integer) layer0.getVertexData(FbxLayerElement.Type.Smoothing, i, polygonVertexIndex, positionIndex, 0);
}
if (layer1 != null) {
irVertex.uv1 = (Vector2f) layer1.getVertexData(FbxLayerElement.Type.UV, i,
polygonVertexIndex, positionIndex, 0);
}
if (boneIndices != null) {
ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex];
ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex];
if (boneIndicesForVertex != null) {
irVertex.boneWeightsIndices = toBoneWeightIndices(boneIndicesForVertex, boneWeightsForVertex);
}
}
irPolygon.vertices[j] = irVertex;
polygonVertexIndex++;
}
newMesh.polygons[i] = irPolygon;
}
// Ensure "inspection vertex" specifies that mesh has bone indices / weights
if (boneIndices != null && newMesh.polygons[0].vertices[0] == null) {
newMesh.polygons[0].vertices[0].boneWeightsIndices = new IrBoneWeightIndex[0];
}
return newMesh;
}
}

@ -0,0 +1,69 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.mesh;
import com.jme3.scene.plugins.fbx.file.FbxElement;
public class FbxMeshUtil {
public static double[] getDoubleArray(FbxElement el) {
if (el.propertiesTypes[0] == 'd') {
// FBX 7.x
return (double[]) el.properties.get(0);
} else if (el.propertiesTypes[0] == 'D') {
// FBX 6.x
double[] doubles = new double[el.propertiesTypes.length];
for (int i = 0; i < doubles.length; i++) {
doubles[i] = (Double) el.properties.get(i);
}
return doubles;
} else {
return null;
}
}
public static int[] getIntArray(FbxElement el) {
if (el.propertiesTypes[0] == 'i') {
// FBX 7.x
return (int[]) el.properties.get(0);
} else if (el.propertiesTypes[0] == 'I') {
// FBX 6.x
int[] ints = new int[el.propertiesTypes.length];
for (int i = 0; i < ints.length; i++) {
ints[i] = (Integer) el.properties.get(i);
}
return ints;
} else {
return null;
}
}
}

@ -0,0 +1,59 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.mesh;
import java.util.Arrays;
import java.util.List;
public final class FbxPolygon {
int[] indices;
@Override
public String toString() {
return Arrays.toString(indices);
}
private static int[] listToArray(List<Integer> indices) {
int[] indicesArray = new int[indices.size()];
for (int i = 0; i < indices.size(); i++) {
indicesArray[i] = indices.get(i);
}
return indicesArray;
}
public static FbxPolygon fromIndices(List<Integer> indices) {
FbxPolygon poly = new FbxPolygon();
poly.indices = listToArray(indices);
return poly;
}
}

@ -0,0 +1,145 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.misc;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxGlobalSettings {
private static final Logger logger = Logger.getLogger(FbxGlobalSettings.class.getName());
private static final Map<Integer, Float> timeModeToFps = new HashMap<Integer, Float>();
static {
timeModeToFps.put(1, 120f);
timeModeToFps.put(2, 100f);
timeModeToFps.put(3, 60f);
timeModeToFps.put(4, 50f);
timeModeToFps.put(5, 48f);
timeModeToFps.put(6, 30f);
timeModeToFps.put(9, 30f / 1.001f);
timeModeToFps.put(10, 25f);
timeModeToFps.put(11, 24f);
timeModeToFps.put(13, 24f / 1.001f);
timeModeToFps.put(14, -1f);
timeModeToFps.put(15, 96f);
timeModeToFps.put(16, 72f);
timeModeToFps.put(17, 60f / 1.001f);
}
public float unitScaleFactor = 1.0f;
public ColorRGBA ambientColor = ColorRGBA.Black;
public float frameRate = 25.0f;
/**
* @return A {@link Transform} that converts from the FBX file coordinate
* system to jME3 coordinate system.
* jME3's coordinate system is:
* <ul>
* <li>Units are specified in meters.</li>
* <li>Orientation is right-handed with Y-up.</li>
* </ul>
*/
public Transform getGlobalTransform() {
// Default unit scale factor is 1 (centimeters),
// convert to meters.
float scale = unitScaleFactor / 100.0f;
// TODO: handle rotation
return new Transform(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(scale, scale, scale));
}
public void fromElement(FbxElement element) {
// jME3 uses a +Y up, -Z forward coordinate system (same as OpenGL)
// Luckily enough, this is also the default for FBX models.
int timeMode = -1;
float customFrameRate = 30.0f;
for (FbxElement e2 : element.getFbxProperties()) {
String propName = (String) e2.properties.get(0);
if (propName.equals("UnitScaleFactor")) {
unitScaleFactor = ((Double) e2.properties.get(4)).floatValue();
if (unitScaleFactor != 100.0f) {
logger.log(Level.WARNING, "FBX model isn't using meters for world units. Scale could be incorrect.");
}
} else if (propName.equals("TimeMode")) {
timeMode = (Integer) e2.properties.get(4);
} else if (propName.equals("CustomFrameRate")) {
float framerate = ((Double) e2.properties.get(4)).floatValue();
if (framerate != -1) {
customFrameRate = framerate;
}
} else if (propName.equals("UpAxis")) {
Integer upAxis = (Integer) e2.properties.get(4);
if (upAxis != 1) {
logger.log(Level.WARNING, "FBX model isn't using Y as up axis. Orientation could be incorrect");
}
} else if (propName.equals("UpAxisSign")) {
Integer upAxisSign = (Integer) e2.properties.get(4);
if (upAxisSign != 1) {
logger.log(Level.WARNING, "FBX model isn't using correct up axis sign. Orientation could be incorrect");
}
} else if (propName.equals("FrontAxis")) {
Integer frontAxis = (Integer) e2.properties.get(4);
if (frontAxis != 2) {
logger.log(Level.WARNING, "FBX model isn't using Z as forward axis. Orientation could be incorrect");
}
} else if (propName.equals("FrontAxisSign")) {
Integer frontAxisSign = (Integer) e2.properties.get(4);
if (frontAxisSign != -1) {
logger.log(Level.WARNING, "FBX model isn't using correct forward axis sign. Orientation could be incorrect");
}
}
}
Float fps = timeModeToFps.get(timeMode);
if (fps != null) {
if (fps == -1f) {
// Using custom framerate
frameRate = customFrameRate;
} else {
// Use FPS from time mode.
frameRate = fps;
}
}
}
}

@ -0,0 +1,617 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.node;
import com.jme3.animation.AnimControl;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.debug.SkeletonDebugger;
import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode;
import com.jme3.scene.plugins.fbx.anim.FbxCluster;
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode;
import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.material.FbxImage;
import com.jme3.scene.plugins.fbx.material.FbxMaterial;
import com.jme3.scene.plugins.fbx.material.FbxTexture;
import com.jme3.scene.plugins.fbx.mesh.FbxMesh;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
import com.jme3.util.IntMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FbxNode extends FbxObject<Spatial> {
private static final Logger logger = Logger.getLogger(FbxNode.class.getName());
private static enum InheritMode {
/**
* Apply parent scale after child rotation.
* This is the only mode correctly supported by jME3.
*/
ScaleAfterChildRotation,
/**
* Apply parent scale before child rotation.
* Not supported by jME3, will cause distortion with
* non-uniform scale. No way around it.
*/
ScaleBeforeChildRotation,
/**
* Do not apply parent scale at all.
* Not supported by jME3, will cause distortion.
* Could be worked around by via:
* <code>jmeChildScale = jmeParentScale / fbxChildScale</code>
*/
NoParentScale
}
private InheritMode inheritMode = InheritMode.ScaleAfterChildRotation;
protected FbxNode parent;
protected List<FbxNode> children = new ArrayList<FbxNode>();
protected List<FbxMaterial> materials = new ArrayList<FbxMaterial>();
protected Map<String, Object> userData = new HashMap<String, Object>();
protected Map<String, List<FbxAnimCurveNode>> propertyToAnimCurveMap = new HashMap<String, List<FbxAnimCurveNode>>();
protected FbxNodeAttribute nodeAttribute;
protected double visibility = 1.0;
/**
* For FBX nodes that contain a skeleton (i.e. FBX limbs).
*/
protected Skeleton skeleton;
protected final Transform jmeWorldNodeTransform = new Transform();
protected final Transform jmeLocalNodeTransform = new Transform();
// optional - used for limbs / bones / skeletons
protected Transform jmeWorldBindPose;
protected Transform jmeLocalBindPose;
// used for debugging only
protected Matrix4f cachedWorldBindPose;
public FbxNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
public Transform computeFbxLocalTransform() {
// TODO: implement the actual algorithm, which is this:
// Render Local Translation =
// Inv Scale Pivot * Lcl Scale * Scale Pivot * Scale Offset * Inv Rota Pivot * Post Rotation * Rotation * Pre Rotation * Rotation Pivot * Rotation Offset * Translation
// LclTranslation,
// LclRotation,
// PreRotation,
// PostRotation,
// RotationPivot,
// RotationOffset,
// LclScaling,
// ScalingPivot,
// ScalingOffset
Matrix4f scaleMat = new Matrix4f();
scaleMat.setScale(jmeLocalNodeTransform.getScale());
Matrix4f rotationMat = new Matrix4f();
rotationMat.setRotationQuaternion(jmeLocalNodeTransform.getRotation());
Matrix4f translationMat = new Matrix4f();
translationMat.setTranslation(jmeLocalNodeTransform.getTranslation());
Matrix4f result = new Matrix4f();
result.multLocal(scaleMat).multLocal(rotationMat).multLocal(translationMat);
Transform t = new Transform();
t.fromTransformMatrix(result);
return t;
}
public void setWorldBindPose(Matrix4f worldBindPose) {
if (cachedWorldBindPose != null) {
if (!cachedWorldBindPose.equals(worldBindPose)) {
throw new UnsupportedOperationException("Bind poses don't match");
}
}
cachedWorldBindPose = worldBindPose;
this.jmeWorldBindPose = new Transform();
this.jmeWorldBindPose.setTranslation(worldBindPose.toTranslationVector());
this.jmeWorldBindPose.setRotation(worldBindPose.toRotationQuat());
this.jmeWorldBindPose.setScale(worldBindPose.toScaleVector());
System.out.println("\tBind Pose for " + getName());
System.out.println(jmeWorldBindPose);
float[] angles = new float[3];
jmeWorldBindPose.getRotation().toAngles(angles);
System.out.println("Angles: " + angles[0] * FastMath.RAD_TO_DEG + ", " +
angles[1] * FastMath.RAD_TO_DEG + ", " +
angles[2] * FastMath.RAD_TO_DEG);
}
public void updateWorldTransforms(Transform jmeParentNodeTransform, Transform parentBindPose) {
Transform fbxLocalTransform = computeFbxLocalTransform();
jmeLocalNodeTransform.set(fbxLocalTransform);
if (jmeParentNodeTransform != null) {
jmeParentNodeTransform = jmeParentNodeTransform.clone();
switch (inheritMode) {
case NoParentScale:
case ScaleAfterChildRotation:
case ScaleBeforeChildRotation:
jmeWorldNodeTransform.set(jmeLocalNodeTransform);
jmeWorldNodeTransform.combineWithParent(jmeParentNodeTransform);
break;
}
} else {
jmeWorldNodeTransform.set(jmeLocalNodeTransform);
}
if (jmeWorldBindPose != null) {
jmeLocalBindPose = new Transform();
// Need to derive local bind pose from world bind pose
// (this is to be expected for FBX limbs)
jmeLocalBindPose.set(jmeWorldBindPose);
jmeLocalBindPose.combineWithParent(parentBindPose.invert());
// Its somewhat odd for the transforms to differ ...
System.out.println("Bind Pose for: " + getName());
if (!jmeLocalBindPose.equals(jmeLocalNodeTransform)) {
System.out.println("Local Bind: " + jmeLocalBindPose);
System.out.println("Local Trans: " + jmeLocalNodeTransform);
}
if (!jmeWorldBindPose.equals(jmeWorldNodeTransform)) {
System.out.println("World Bind: " + jmeWorldBindPose);
System.out.println("World Trans: " + jmeWorldNodeTransform);
}
} else {
// World pose derived from local transforms
// (this is to be expected for FBX nodes)
jmeLocalBindPose = new Transform();
jmeWorldBindPose = new Transform();
jmeLocalBindPose.set(jmeLocalNodeTransform);
if (parentBindPose != null) {
jmeWorldBindPose.set(jmeLocalNodeTransform);
jmeWorldBindPose.combineWithParent(parentBindPose);
} else {
jmeWorldBindPose.set(jmeWorldNodeTransform);
}
}
for (FbxNode child : children) {
child.updateWorldTransforms(jmeWorldNodeTransform, jmeWorldBindPose);
}
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
Vector3f localTranslation = new Vector3f();
Quaternion localRotation = new Quaternion();
Vector3f localScale = new Vector3f(Vector3f.UNIT_XYZ);
Quaternion preRotation = new Quaternion();
for (FbxElement e2 : element.getFbxProperties()) {
String propName = (String) e2.properties.get(0);
String type = (String) e2.properties.get(3);
if (propName.equals("Lcl Translation")) {
double x = (Double) e2.properties.get(4);
double y = (Double) e2.properties.get(5);
double z = (Double) e2.properties.get(6);
localTranslation.set((float) x, (float) y, (float) z); //.divideLocal(unitSize);
} else if (propName.equals("Lcl Rotation")) {
double x = (Double) e2.properties.get(4);
double y = (Double) e2.properties.get(5);
double z = (Double) e2.properties.get(6);
localRotation.fromAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD);
} else if (propName.equals("Lcl Scaling")) {
double x = (Double) e2.properties.get(4);
double y = (Double) e2.properties.get(5);
double z = (Double) e2.properties.get(6);
localScale.set((float) x, (float) y, (float) z); //.multLocal(unitSize);
} else if (propName.equals("PreRotation")) {
double x = (Double) e2.properties.get(4);
double y = (Double) e2.properties.get(5);
double z = (Double) e2.properties.get(6);
preRotation.set(FbxNodeUtil.quatFromBoneAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD));
} else if (propName.equals("InheritType")) {
int inheritType = (Integer) e2.properties.get(4);
inheritMode = InheritMode.values()[inheritType];
} else if (propName.equals("Visibility")) {
visibility = (Double) e2.properties.get(4);
} else if (type.contains("U")) {
String userDataKey = (String) e2.properties.get(0);
String userDataType = (String) e2.properties.get(1);
Object userDataValue;
if (userDataType.equals("KString")) {
userDataValue = (String) e2.properties.get(4);
} else if (userDataType.equals("int")) {
userDataValue = (Integer) e2.properties.get(4);
} else if (userDataType.equals("double")) {
// NOTE: jME3 does not support doubles in UserData.
// Need to convert to float.
userDataValue = ((Double) e2.properties.get(4)).floatValue();
} else if (userDataType.equals("Vector")) {
float x = ((Double) e2.properties.get(4)).floatValue();
float y = ((Double) e2.properties.get(5)).floatValue();
float z = ((Double) e2.properties.get(6)).floatValue();
userDataValue = new Vector3f(x, y, z);
} else {
logger.log(Level.WARNING, "Unsupported user data type: {0}. Ignoring.", userDataType);
continue;
}
userData.put(userDataKey, userDataValue);
}
}
// Create local transform
// TODO: take into account Maya-style transforms (pre / post rotation ..)
jmeLocalNodeTransform.setTranslation(localTranslation);
jmeLocalNodeTransform.setRotation(localRotation);
jmeLocalNodeTransform.setScale(localScale);
if (element.getChildById("Vertices") != null) {
// This is an old-style FBX 6.1
// Meshes could be embedded inside the node..
// Inject the mesh into ourselves..
FbxMesh mesh = new FbxMesh(assetManager, sceneFolderName);
mesh.fromElement(element);
connectObject(mesh);
}
}
private Spatial tryCreateGeometry(int materialIndex, Mesh jmeMesh, boolean single) {
// Map meshes without material indices to material 0.
if (materialIndex == -1) {
materialIndex = 0;
}
Material jmeMat;
if (materialIndex >= materials.size()) {
// Material index does not exist. Create default material.
jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
jmeMat.setReceivesShadows(true);
} else {
FbxMaterial fbxMat = materials.get(materialIndex);
jmeMat = fbxMat.getJmeObject();
}
String geomName = getName();
if (single) {
geomName += "-submesh";
} else {
geomName += "-mat-" + materialIndex + "-submesh";
}
Spatial spatial = new Geometry(geomName, jmeMesh);
spatial.setMaterial(jmeMat);
if (jmeMat.isTransparent()) {
spatial.setQueueBucket(Bucket.Transparent);
}
if (jmeMat.isReceivesShadows()) {
spatial.setShadowMode(ShadowMode.Receive);
}
spatial.updateModelBound();
return spatial;
}
/**
* If this geometry node is deformed by a skeleton, this
* returns the node containing the skeleton.
*
* In jME3, a mesh can be deformed by a skeleton only if it is
* a child of the node containing the skeleton. However, this
* is not a requirement in FBX, so we have to modify the scene graph
* of the loaded model to adjust for this.
* This happens automatically in
* {@link #createScene(com.jme3.scene.plugins.fbx.node.FbxNode)}.
*
* @return The model this node would like to be a child of, or null
* if no preferred parent.
*/
public FbxNode getPreferredParent() {
if (!(nodeAttribute instanceof FbxMesh)) {
return null;
}
FbxMesh fbxMesh = (FbxMesh) nodeAttribute;
FbxSkinDeformer deformer = fbxMesh.getSkinDeformer();
FbxNode preferredParent = null;
if (deformer != null) {
for (FbxCluster cluster : deformer.getJmeObject()) {
FbxLimbNode limb = cluster.getLimb();
if (preferredParent == null) {
preferredParent = limb.getSkeletonHolder();
} else if (preferredParent != limb.getSkeletonHolder()) {
logger.log(Level.WARNING, "A mesh is being deformed by multiple skeletons. "
+ "Only one skeleton will work, ignoring other skeletons.");
}
}
}
return preferredParent;
}
@Override
public Spatial toJmeObject() {
Spatial spatial;
if (nodeAttribute instanceof FbxMesh) {
FbxMesh fbxMesh = (FbxMesh) nodeAttribute;
IntMap<Mesh> jmeMeshes = fbxMesh.getJmeObject();
if (jmeMeshes == null || jmeMeshes.size() == 0) {
// No meshes found on FBXMesh (??)
logger.log(Level.WARNING, "No meshes could be loaded. Creating empty node.");
spatial = new Node(getName() + "-node");
} else {
// Multiple jME3 geometries required for a single FBXMesh.
String nodeName;
if (children.isEmpty()) {
nodeName = getName() + "-mesh";
} else {
nodeName = getName() + "-node";
}
Node node = new Node(nodeName);
boolean singleMesh = jmeMeshes.size() == 1;
for (IntMap.Entry<Mesh> meshInfo : jmeMeshes) {
node.attachChild(tryCreateGeometry(meshInfo.getKey(), meshInfo.getValue(), singleMesh));
}
spatial = node;
}
} else {
if (nodeAttribute != null) {
// Just specifies that this is a "null" node.
nodeAttribute.getJmeObject();
}
// TODO: handle other node attribute types.
// right now everything we don't know about gets converted
// to jME3 Node.
spatial = new Node(getName() + "-node");
}
if (!children.isEmpty()) {
// Check uniform scale.
// Although, if inheritType is 0 (eInheritRrSs)
// it might not be a problem.
Vector3f localScale = jmeLocalNodeTransform.getScale();
if (!FastMath.approximateEquals(localScale.x, localScale.y) ||
!FastMath.approximateEquals(localScale.x, localScale.z)) {
logger.log(Level.WARNING, "Non-uniform scale detected on parent node. " +
"The model may appear distorted.");
}
}
spatial.setLocalTransform(jmeLocalNodeTransform);
if (visibility == 0.0) {
spatial.setCullHint(CullHint.Always);
}
for (Map.Entry<String, Object> userDataEntry : userData.entrySet()) {
spatial.setUserData(userDataEntry.getKey(), userDataEntry.getValue());
}
return spatial;
}
/**
* Create jME3 Skeleton objects on the scene.
*
* Goes through the scene graph and finds limbs that are
* attached to FBX nodes, then creates a Skeleton on the node
* based on the child limbs.
*
* Must be called prior to calling
* {@link #createScene(com.jme3.scene.plugins.fbx.node.FbxNode)}.
*
* @param fbxNode The root FBX node.
*/
public static void createSkeletons(FbxNode fbxNode) {
boolean createSkeleton = false;
for (FbxNode fbxChild : fbxNode.children) {
if (fbxChild instanceof FbxLimbNode) {
createSkeleton = true;
} else {
createSkeletons(fbxChild);
}
}
if (createSkeleton) {
if (fbxNode.skeleton != null) {
throw new UnsupportedOperationException();
}
fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode);
System.out.println("created skeleton: " + fbxNode.skeleton);
}
}
private static void relocateSpatial(Spatial spatial,
Transform originalWorldTransform, Transform newWorldTransform) {
Transform localTransform = new Transform();
localTransform.set(originalWorldTransform);
localTransform.combineWithParent(newWorldTransform.invert());
spatial.setLocalTransform(localTransform);
}
public static Spatial createScene(FbxNode fbxNode) {
Spatial jmeSpatial = fbxNode.getJmeObject();
if (jmeSpatial instanceof Node) {
// Attach children to Node
Node jmeNode = (Node) jmeSpatial;
for (FbxNode fbxChild : fbxNode.children) {
if (!(fbxChild instanceof FbxLimbNode)) {
createScene(fbxChild);
FbxNode preferredParent = fbxChild.getPreferredParent();
Spatial jmeChild = fbxChild.getJmeObject();
if (preferredParent != null) {
System.out.println("Preferred parent for " + fbxChild + " is " + preferredParent);
Node jmePreferredParent = (Node) preferredParent.getJmeObject();
relocateSpatial(jmeChild, fbxChild.jmeWorldNodeTransform,
preferredParent.jmeWorldNodeTransform);
jmePreferredParent.attachChild(jmeChild);
} else {
jmeNode.attachChild(jmeChild);
}
}
}
}
if (fbxNode.skeleton != null) {
jmeSpatial.addControl(new AnimControl(fbxNode.skeleton));
jmeSpatial.addControl(new SkeletonControl(fbxNode.skeleton));
SkeletonDebugger sd = new SkeletonDebugger("debug", fbxNode.skeleton);
Material mat = new Material(fbxNode.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.getAdditionalRenderState().setWireframe(true);
mat.getAdditionalRenderState().setDepthTest(false);
mat.setColor("Color", ColorRGBA.Green);
sd.setMaterial(mat);
((Node)jmeSpatial).attachChild(sd);
}
return jmeSpatial;
}
// public SceneLoader.Limb toLimb() {
// SceneLoader.Limb limb = new SceneLoader.Limb();
// limb.name = getName();
// Quaternion rotation = preRotation.mult(localRotation);
// limb.bindTransform = new Transform(localTranslation, rotation, localScale);
// return limb;
// }
public Skeleton getJmeSkeleton() {
return skeleton;
}
public List<FbxNode> getChildren() {
return children;
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxNode) {
// Scene Graph Object
FbxNode childNode = (FbxNode) object;
if (childNode.parent != null) {
throw new IllegalStateException("Cannot attach " + childNode
+ " to " + this + ". It is already "
+ "attached to " + childNode.parent);
}
childNode.parent = this;
children.add(childNode);
} else if (object instanceof FbxNodeAttribute) {
// Node Attribute
if (nodeAttribute != null) {
throw new IllegalStateException("An FBXNodeAttribute (" + nodeAttribute + ")" +
" is already attached to " + this + ". " +
"Only one attribute allowed per node.");
}
nodeAttribute = (FbxNodeAttribute) object;
if (nodeAttribute instanceof FbxNullAttribute) {
nodeAttribute.getJmeObject();
}
} else if (object instanceof FbxMaterial) {
materials.add((FbxMaterial) object);
} else if (object instanceof FbxImage || object instanceof FbxTexture) {
// Ignore - attaching textures to nodes is legacy feature.
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
// Only allowed to connect local transform properties to object
// (FbxAnimCurveNode)
if (object instanceof FbxAnimCurveNode) {
FbxAnimCurveNode curveNode = (FbxAnimCurveNode) object;
if (property.equals("Lcl Translation")
|| property.equals("Lcl Rotation")
|| property.equals("Lcl Scaling")) {
List<FbxAnimCurveNode> curveNodes = propertyToAnimCurveMap.get(property);
if (curveNodes == null) {
curveNodes = new ArrayList<FbxAnimCurveNode>();
curveNodes.add(curveNode);
propertyToAnimCurveMap.put(property, curveNodes);
}
curveNodes.add(curveNode);
// Make sure the curve knows about it animating
// this node as well.
curveNode.addInfluencedNode(this, property);
} else {
logger.log(Level.WARNING, "Animating the property ''{0}'' is not "
+ "supported. Ignoring.", property);
}
} else {
unsupportedConnectObjectProperty(object, property);
}
}
}

@ -0,0 +1,41 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.node;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
public abstract class FbxNodeAttribute<JT> extends FbxObject<JT> {
public FbxNodeAttribute(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
}

@ -0,0 +1,61 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.node;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
public class FbxNodeUtil {
public static Quaternion quatFromBoneAngles(float xAngle, float yAngle, float zAngle) {
float angle;
float sinY, sinZ, sinX, cosY, cosZ, cosX;
angle = zAngle * 0.5f;
sinZ = FastMath.sin(angle);
cosZ = FastMath.cos(angle);
angle = yAngle * 0.5f;
sinY = FastMath.sin(angle);
cosY = FastMath.cos(angle);
angle = xAngle * 0.5f;
sinX = FastMath.sin(angle);
cosX = FastMath.cos(angle);
float cosYXcosZ = cosY * cosZ;
float sinYXsinZ = sinY * sinZ;
float cosYXsinZ = cosY * sinZ;
float sinYXcosZ = sinY * cosZ;
// For some reason bone space is differ, this is modified formulas
float w = (cosYXcosZ * cosX + sinYXsinZ * sinX);
float x = (cosYXcosZ * sinX - sinYXsinZ * cosX);
float y = (sinYXcosZ * cosX + cosYXsinZ * sinX);
float z = (cosYXsinZ * cosX - sinYXcosZ * sinX);
return new Quaternion(x, y, z, w).normalizeLocal();
}
}

@ -0,0 +1,59 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.node;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.obj.FbxObject;
public class FbxNullAttribute extends FbxNodeAttribute<Object> {
public FbxNullAttribute(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
protected Object toJmeObject() {
// No data in a "Null" attribute.
return new Object();
}
@Override
public void connectObject(FbxObject object) {
throw new UnsupportedOperationException();
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,45 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.node;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.file.FbxId;
public class FbxRootNode extends FbxNode {
public FbxRootNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
this.id = FbxId.ROOT;
this.className = "Model";
this.name = "Scene";
this.subclassName = "";
}
}

@ -0,0 +1,144 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.obj;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.file.FbxId;
import java.util.logging.Logger;
public abstract class FbxObject<JT> {
private static final Logger logger = Logger.getLogger(FbxObject.class.getName());
protected AssetManager assetManager;
protected String sceneFolderName;
protected FbxId id;
protected String name;
protected String className;
protected String subclassName;
protected JT jmeObject; // lazily initialized
protected FbxObject(AssetManager assetManager, String sceneFolderName) {
this.assetManager = assetManager;
this.sceneFolderName = sceneFolderName;
}
public FbxId getId() {
return id;
}
public String getName() {
return name;
}
public String getClassName() {
return className;
}
public String getSubclassName() {
return subclassName;
}
public String getFullClassName() {
if (subclassName.equals("")) {
return className;
} else {
return subclassName + " : " + className;
}
}
@Override
public String toString() {
return name + " (" + id + ")";
}
protected void fromElement(FbxElement element) {
id = FbxId.getObjectId(element);
String nameAndClass;
if (element.propertiesTypes.length == 3) {
nameAndClass = (String) element.properties.get(1);
subclassName = (String) element.properties.get(2);
} else if (element.propertiesTypes.length == 2) {
nameAndClass = (String) element.properties.get(0);
subclassName = (String) element.properties.get(1);
} else {
throw new UnsupportedOperationException("This is not an FBX object: " + element.id);
}
int splitter = nameAndClass.indexOf("\u0000\u0001");
if (splitter != -1) {
name = nameAndClass.substring(0, splitter);
className = nameAndClass.substring(splitter + 2);
} else {
name = nameAndClass;
className = null;
}
}
public final JT getJmeObject() {
if (jmeObject == null) {
jmeObject = toJmeObject();
if (jmeObject == null) {
throw new UnsupportedOperationException("FBX object subclass "
+ "failed to resolve to a jME3 object");
}
}
return jmeObject;
}
public final boolean isJmeObjectCreated() {
return jmeObject != null;
}
protected final void unsupportedConnectObject(FbxObject object) {
throw new IllegalArgumentException("Cannot attach objects of this class (" +
object.getFullClassName() +
") to " + getClass().getSimpleName());
}
protected final void unsupportedConnectObjectProperty(FbxObject object, String property) {
throw new IllegalArgumentException("Cannot attach objects of this class (" +
object.getFullClassName() +
") to property " + getClass().getSimpleName() +
"[\"" + property + "\"]");
}
protected abstract JT toJmeObject();
public abstract void connectObject(FbxObject object);
public abstract void connectObjectProperty(FbxObject object, String property);
}

@ -0,0 +1,209 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.obj;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.anim.FbxAnimCurve;
import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode;
import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer;
import com.jme3.scene.plugins.fbx.anim.FbxAnimStack;
import com.jme3.scene.plugins.fbx.anim.FbxBindPose;
import com.jme3.scene.plugins.fbx.anim.FbxCluster;
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode;
import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer;
import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.material.FbxImage;
import com.jme3.scene.plugins.fbx.material.FbxMaterial;
import com.jme3.scene.plugins.fbx.material.FbxTexture;
import com.jme3.scene.plugins.fbx.mesh.FbxMesh;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import com.jme3.scene.plugins.fbx.node.FbxNullAttribute;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Responsible for producing FBX objects given an FBXElement.
*/
public final class FbxObjectFactory {
private static final Logger logger = Logger.getLogger(FbxObjectFactory.class.getName());
private static Class<? extends FbxObject> getImplementingClass(String elementName, String subclassName) {
if (elementName.equals("NodeAttribute")) {
if (subclassName.equals("Root")) {
// Root of skeleton, may not actually be set.
return FbxNullAttribute.class;
} else if (subclassName.equals("LimbNode")) {
// Specifies some limb attributes, optional.
return FbxNullAttribute.class;
} else if (subclassName.equals("Null")) {
// An "Empty" or "Node" without any specific behavior.
return FbxNullAttribute.class;
} else if (subclassName.equals("IKEffector") ||
subclassName.equals("FKEffector")) {
// jME3 does not support IK.
return FbxNullAttribute.class;
} else {
// NodeAttribute - Unknown
logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName);
return FbxUnknownObject.class;
}
} else if (elementName.equals("Geometry") && subclassName.equals("Mesh")) {
// NodeAttribute - Mesh Data
return FbxMesh.class;
} else if (elementName.equals("Model")) {
// Scene Graph Node
// Determine specific subclass (e.g. Mesh, Null, or LimbNode?)
if (subclassName.equals("LimbNode")) {
return FbxLimbNode.class; // Child Bone of Skeleton?
} else {
return FbxNode.class;
}
} else if (elementName.equals("Pose")) {
if (subclassName.equals("BindPose")) {
// Bind Pose Information
return FbxBindPose.class;
} else {
// Rest Pose Information
// OR
// Other Data (???)
logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName);
return FbxUnknownObject.class;
}
} else if (elementName.equals("Material")) {
return FbxMaterial.class;
} else if (elementName.equals("Deformer")) {
// Deformer
if (subclassName.equals("Skin")) {
// FBXSkinDeformer (mapping between FBXMesh & FBXClusters)
return FbxSkinDeformer.class;
} else if (subclassName.equals("Cluster")) {
// Cluster (aka mapping between FBXMesh vertices & weights for bone)
return FbxCluster.class;
} else {
logger.log(Level.WARNING, "Unknown deformer subclass: {0}. Ignoring.", subclassName);
return FbxUnknownObject.class;
}
} else if (elementName.equals("Video")) {
if (subclassName.equals("Clip")) {
return FbxImage.class;
} else {
logger.log(Level.WARNING, "Unknown object subclass: {0}. Ignoring.", subclassName);
return FbxUnknownObject.class;
}
} else if (elementName.equals("Texture")) {
return FbxTexture.class;
} else if (elementName.equals("AnimationStack")) {
// AnimationStack (jME Animation)
return FbxAnimStack.class;
} else if (elementName.equals("AnimationLayer")) {
// AnimationLayer (for blended animation - not supported)
return FbxAnimLayer.class;
} else if (elementName.equals("AnimationCurveNode")) {
// AnimationCurveNode
return FbxAnimCurveNode.class;
} else if (elementName.equals("AnimationCurve")) {
// AnimationCurve (Data)
return FbxAnimCurve.class;
} else if (elementName.equals("SceneInfo")) {
// Old-style FBX 6.1 uses this. Nothing useful here.
return FbxUnknownObject.class;
} else {
logger.log(Level.WARNING, "Unknown object class: {0}. Ignoring.", elementName);
return FbxUnknownObject.class;
}
}
/**
* Automatically create an FBXObject by inspecting its class / subclass
* properties.
*
* @param element The element from which to create an object.
* @param assetManager AssetManager to load dependent resources
* @param sceneFolderName Folder relative to which resources shall be loaded
* @return The object, or null if not supported (?)
*/
public static FbxObject createObject(FbxElement element, AssetManager assetManager, String sceneFolderName) {
String elementName = element.id;
String subclassName;
if (element.propertiesTypes.length == 3) {
// FBX 7.x (all objects start with Long ID)
subclassName = (String) element.properties.get(2);
} else if (element.propertiesTypes.length == 2) {
// FBX 6.x (objects only have name and subclass)
subclassName = (String) element.properties.get(1);
} else {
// Not an object or invalid data.
return null;
}
Class<? extends FbxObject> javaFbxClass = getImplementingClass(elementName, subclassName);
if (javaFbxClass != null) {
try {
// This object is supported by FBX importer, create new instance.
// Import the data into the object from the element, then return it.
Constructor<? extends FbxObject> ctor = javaFbxClass.getConstructor(AssetManager.class, String.class);
FbxObject obj = ctor.newInstance(assetManager, sceneFolderName);
obj.fromElement(element);
String subClassName = elementName + ", " + subclassName;
if (obj.assetManager == null) {
throw new IllegalStateException("FBXObject subclass (" + subClassName +
") forgot to call super() in their constructor");
} else if (obj.className == null) {
throw new IllegalStateException("FBXObject subclass (" + subClassName +
") forgot to call super.fromElement() in their fromElement() implementation");
}
return obj;
} catch (InvocationTargetException ex) {
// Programmer error.
throw new IllegalStateException(ex);
} catch (NoSuchMethodException ex) {
// Programmer error.
throw new IllegalStateException(ex);
} catch (InstantiationException ex) {
// Programmer error.
throw new IllegalStateException(ex);
} catch (IllegalAccessException ex) {
// Programmer error.
throw new IllegalStateException(ex);
}
}
// Not supported object.
return null;
}
}

@ -0,0 +1,54 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.obj;
import com.jme3.asset.AssetManager;
public class FbxUnknownObject extends FbxObject<Void> {
public FbxUnknownObject(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
protected Void toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
}
}

@ -0,0 +1,89 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins;
public class IrBoneWeightIndex implements Cloneable, Comparable<IrBoneWeightIndex> {
int boneIndex;
float boneWeight;
public IrBoneWeightIndex(int boneIndex, float boneWeight) {
this.boneIndex = boneIndex;
this.boneWeight = boneWeight;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new AssertionError(ex);
}
}
@Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + this.boneIndex;
hash = 23 * hash + Float.floatToIntBits(this.boneWeight);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final IrBoneWeightIndex other = (IrBoneWeightIndex) obj;
if (this.boneIndex != other.boneIndex) {
return false;
}
if (Float.floatToIntBits(this.boneWeight) != Float.floatToIntBits(other.boneWeight)) {
return false;
}
return true;
}
@Override
public int compareTo(IrBoneWeightIndex o) {
if (boneWeight < o.boneWeight) {
return 1;
} else if (boneWeight > o.boneWeight) {
return -1;
} else {
return 0;
}
}
}

@ -0,0 +1,46 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins;
public class IrMesh {
public IrPolygon[] polygons;
public IrMesh deepClone() {
IrMesh m = new IrMesh();
m.polygons = new IrPolygon[polygons.length];
for (int i = 0; i < polygons.length; i++) {
m.polygons[i] = polygons[i].deepClone();
}
return m;
}
}

@ -0,0 +1,46 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins;
public class IrPolygon {
public IrVertex[] vertices;
public IrPolygon deepClone() {
IrPolygon p = new IrPolygon();
p.vertices = new IrVertex[vertices.length];
for (int i = 0; i < vertices.length; i++) {
p.vertices[i] = vertices[i].deepClone();
}
return p;
}
}

@ -0,0 +1,400 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins;
import com.jme3.math.Vector4f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.scene.mesh.IndexIntBuffer;
import com.jme3.scene.mesh.IndexShortBuffer;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class IrUtils {
private static final Logger logger = Logger.getLogger(IrUtils.class.getName());
private IrUtils() { }
private static IrPolygon[] quadToTri(IrPolygon quad) {
if (quad.vertices.length == 3) {
throw new IllegalStateException("Already a triangle");
}
IrPolygon[] t = new IrPolygon[]{ new IrPolygon(), new IrPolygon() };
t[0].vertices = new IrVertex[3];
t[1].vertices = new IrVertex[3];
IrVertex v0 = quad.vertices[0];
IrVertex v1 = quad.vertices[1];
IrVertex v2 = quad.vertices[2];
IrVertex v3 = quad.vertices[3];
// find the pair of verticies that is closest to each over
// v0 and v2
// OR
// v1 and v3
float d1 = v0.pos.distanceSquared(v2.pos);
float d2 = v1.pos.distanceSquared(v3.pos);
if (d1 < d2) {
// v0 is close to v2
// put an edge in v0, v2
t[0].vertices[0] = v0;
t[0].vertices[1] = v1;
t[0].vertices[2] = v3;
t[1].vertices[0] = v1;
t[1].vertices[1] = v2;
t[1].vertices[2] = v3;
} else {
// put an edge in v1, v3
t[0].vertices[0] = v0;
t[0].vertices[1] = v1;
t[0].vertices[2] = v2;
t[1].vertices[0] = v0;
t[1].vertices[1] = v2;
t[1].vertices[2] = v3;
}
return t;
}
/**
* Applies smoothing groups to vertex normals.
*/
public static IrMesh applySmoothingGroups(IrMesh mesh) {
return null;
}
private static void toTangentsWithParity(IrVertex vertex) {
if (vertex.tang != null && vertex.bitang != null) {
float wCoord = vertex.norm.cross(vertex.tang).dot(vertex.bitang) < 0f ? -1f : 1f;
vertex.tang4d = new Vector4f(vertex.tang.x, vertex.tang.y, vertex.tang.z, wCoord);
vertex.tang = null;
vertex.bitang = null;
}
}
public static void toTangentsWithParity(IrMesh mesh) {
for (IrPolygon polygon : mesh.polygons) {
for (IrVertex vertex : polygon.vertices) {
toTangentsWithParity(vertex);
}
}
}
private static void trimBoneWeights(IrVertex vertex) {
if (vertex.boneWeightsIndices == null) {
return;
}
IrBoneWeightIndex[] boneWeightsIndices = vertex.boneWeightsIndices;
if (boneWeightsIndices.length <= 4) {
return;
}
// Sort by weight
boneWeightsIndices = Arrays.copyOf(boneWeightsIndices, boneWeightsIndices.length);
Arrays.sort(boneWeightsIndices);
// Trim to four weights at most
boneWeightsIndices = Arrays.copyOf(boneWeightsIndices, 4);
// Renormalize weights
float sum = 0;
for (int i = 0; i < boneWeightsIndices.length; i++) {
sum += boneWeightsIndices[i].boneWeight;
}
if (sum != 1f) {
float sumToB = sum == 0 ? 0 : 1f / sum;
for (int i = 0; i < boneWeightsIndices.length; i++) {
IrBoneWeightIndex original = boneWeightsIndices[i];
boneWeightsIndices[i] = new IrBoneWeightIndex(original.boneIndex, original.boneWeight * sumToB);
}
}
vertex.boneWeightsIndices = boneWeightsIndices;
}
/**
* Removes low bone weights from mesh, leaving only 4 bone weights at max.
*/
public static void trimBoneWeights(IrMesh mesh) {
for (IrPolygon polygon : mesh.polygons) {
for (IrVertex vertex : polygon.vertices) {
trimBoneWeights(vertex);
}
}
}
/**
* Convert mesh from quads / triangles to triangles only.
*/
public static void triangulate(IrMesh mesh) {
List<IrPolygon> newPolygons = new ArrayList<IrPolygon>(mesh.polygons.length);
for (IrPolygon inputPoly : mesh.polygons) {
if (inputPoly.vertices.length == 4) {
IrPolygon[] tris = quadToTri(inputPoly);
newPolygons.add(tris[0]);
newPolygons.add(tris[1]);
} else if (inputPoly.vertices.length == 3) {
newPolygons.add(inputPoly);
} else {
// N-gon. We have to ignore it..
logger.log(Level.WARNING, "N-gon encountered, ignoring. "
+ "The mesh may not appear correctly. "
+ "Triangulate your model prior to export.");
}
}
mesh.polygons = new IrPolygon[newPolygons.size()];
newPolygons.toArray(mesh.polygons);
}
/**
* Separate mesh with multiple materials into multiple meshes each with
* one material each.
*
* Polygons without a material will be added to key = -1.
*/
public static IntMap<IrMesh> splitByMaterial(IrMesh mesh) {
IntMap<List<IrPolygon>> materialToPolyList = new IntMap<List<IrPolygon>>();
for (IrPolygon polygon : mesh.polygons) {
int materialIndex = -1;
for (IrVertex vertex : polygon.vertices) {
if (vertex.material == null) {
continue;
}
if (materialIndex == -1) {
materialIndex = vertex.material;
} else if (materialIndex != vertex.material) {
throw new UnsupportedOperationException("Multiple materials "
+ "assigned to the same polygon");
}
}
List<IrPolygon> polyList = materialToPolyList.get(materialIndex);
if (polyList == null) {
polyList = new ArrayList<IrPolygon>();
materialToPolyList.put(materialIndex, polyList);
}
polyList.add(polygon);
}
IntMap<IrMesh> materialToMesh = new IntMap<IrMesh>();
for (IntMap.Entry<List<IrPolygon>> entry : materialToPolyList) {
int key = entry.getKey();
List<IrPolygon> polygons = entry.getValue();
if (polygons.size() > 0) {
IrMesh newMesh = new IrMesh();
newMesh.polygons = new IrPolygon[polygons.size()];
polygons.toArray(newMesh.polygons);
materialToMesh.put(key, newMesh);
}
}
return materialToMesh;
}
/**
* Convert IrMesh to jME3 mesh.
*/
public static Mesh convertIrMeshToJmeMesh(IrMesh mesh) {
Map<IrVertex, Integer> vertexToVertexIndex = new HashMap<IrVertex, Integer>();
List<IrVertex> vertices = new ArrayList<IrVertex>();
List<Integer> indexes = new ArrayList<Integer>();
int vertexIndex = 0;
for (IrPolygon polygon : mesh.polygons) {
if (polygon.vertices.length != 3) {
throw new UnsupportedOperationException("IrMesh must be triangulated first");
}
for (IrVertex vertex : polygon.vertices) {
// Is this vertex already indexed?
Integer existingIndex = vertexToVertexIndex.get(vertex);
if (existingIndex == null) {
// Not indexed yet, allocate index.
indexes.add(vertexIndex);
vertexToVertexIndex.put(vertex, vertexIndex);
vertices.add(vertex);
vertexIndex++;
} else {
// Index already allocated for this vertex, reuse it.
indexes.add(existingIndex);
}
}
}
Mesh jmeMesh = new Mesh();
jmeMesh.setMode(Mesh.Mode.Triangles);
FloatBuffer posBuf = null;
FloatBuffer normBuf = null;
FloatBuffer tangBuf = null;
FloatBuffer uv0Buf = null;
FloatBuffer uv1Buf = null;
ByteBuffer colorBuf = null;
ByteBuffer boneIndices = null;
FloatBuffer boneWeights = null;
IndexBuffer indexBuf = null;
IrVertex inspectionVertex = vertices.get(0);
if (inspectionVertex.pos != null) {
posBuf = BufferUtils.createVector3Buffer(vertices.size());
jmeMesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
}
if (inspectionVertex.norm != null) {
normBuf = BufferUtils.createVector3Buffer(vertices.size());
jmeMesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf);
}
if (inspectionVertex.tang4d != null) {
tangBuf = BufferUtils.createFloatBuffer(vertices.size() * 4);
jmeMesh.setBuffer(VertexBuffer.Type.Tangent, 4, tangBuf);
}
if (inspectionVertex.tang != null || inspectionVertex.bitang != null) {
throw new IllegalStateException("Mesh is using 3D tangents, must be converted to 4D tangents first.");
}
if (inspectionVertex.uv0 != null) {
uv0Buf = BufferUtils.createVector2Buffer(vertices.size());
jmeMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv0Buf);
}
if (inspectionVertex.uv1 != null) {
uv1Buf = BufferUtils.createVector2Buffer(vertices.size());
jmeMesh.setBuffer(VertexBuffer.Type.TexCoord2, 2, uv1Buf);
}
if (inspectionVertex.color != null) {
colorBuf = BufferUtils.createByteBuffer(vertices.size() * 4);
jmeMesh.setBuffer(VertexBuffer.Type.Color, 4, colorBuf);
jmeMesh.getBuffer(VertexBuffer.Type.Color).setNormalized(true);
}
if (inspectionVertex.boneWeightsIndices != null) {
boneIndices = BufferUtils.createByteBuffer(vertices.size() * 4);
boneWeights = BufferUtils.createFloatBuffer(vertices.size() * 4);
jmeMesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndices);
jmeMesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeights);
//creating empty buffers for HW skinning
//the buffers will be setup if ever used.
VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight);
VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex);
//setting usage to cpuOnly so that the buffer is not send empty to the GPU
indicesHW.setUsage(VertexBuffer.Usage.CpuOnly);
weightsHW.setUsage(VertexBuffer.Usage.CpuOnly);
jmeMesh.setBuffer(weightsHW);
jmeMesh.setBuffer(indicesHW);
}
if (vertices.size() >= 65536) {
// too many verticies: use intbuffer instead of shortbuffer
IntBuffer ib = BufferUtils.createIntBuffer(indexes.size());
jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, ib);
indexBuf = new IndexIntBuffer(ib);
} else {
ShortBuffer sb = BufferUtils.createShortBuffer(indexes.size());
jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, sb);
indexBuf = new IndexShortBuffer(sb);
}
jmeMesh.setStatic();
int maxBonesPerVertex = -1;
for (IrVertex vertex : vertices) {
if (posBuf != null) {
posBuf.put(vertex.pos.x).put(vertex.pos.y).put(vertex.pos.z);
}
if (normBuf != null) {
normBuf.put(vertex.norm.x).put(vertex.norm.y).put(vertex.norm.z);
}
if (tangBuf != null) {
tangBuf.put(vertex.tang4d.x).put(vertex.tang4d.y).put(vertex.tang4d.z).put(vertex.tang4d.w);
}
if (uv0Buf != null) {
uv0Buf.put(vertex.uv0.x).put(vertex.uv0.y);
}
if (uv1Buf != null) {
uv1Buf.put(vertex.uv1.x).put(vertex.uv1.y);
}
if (colorBuf != null) {
colorBuf.putInt(vertex.color.asIntABGR());
}
if (boneIndices != null) {
if (vertex.boneWeightsIndices != null) {
if (vertex.boneWeightsIndices.length > 4) {
throw new UnsupportedOperationException("Mesh uses more than 4 weights per bone. " +
"Call trimBoneWeights() to allieviate this");
}
for (int i = 0; i < vertex.boneWeightsIndices.length; i++) {
boneIndices.put((byte) (vertex.boneWeightsIndices[i].boneIndex & 0xFF));
boneWeights.put(vertex.boneWeightsIndices[i].boneWeight);
}
for (int i = 0; i < 4 - vertex.boneWeightsIndices.length; i++) {
boneIndices.put((byte)0);
boneWeights.put(0f);
}
} else {
boneIndices.putInt(0);
boneWeights.put(0f).put(0f).put(0f).put(0f);
}
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
}
}
for (int i = 0; i < indexes.size(); i++) {
indexBuf.put(i, indexes.get(i));
}
jmeMesh.updateCounts();
jmeMesh.updateBound();
if (boneIndices != null) {
jmeMesh.setMaxNumWeights(maxBonesPerVertex);
jmeMesh.prepareForAnim(true);
jmeMesh.generateBindPose(true);
}
return jmeMesh;
}
}

@ -0,0 +1,170 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import java.util.Arrays;
public class IrVertex implements Cloneable {
public Vector3f pos;
public Vector3f norm;
public Vector4f tang4d;
public Vector3f tang;
public Vector3f bitang;
public Vector2f uv0;
public Vector2f uv1;
public ColorRGBA color;
public Integer material;
public Integer smoothing;
public IrBoneWeightIndex[] boneWeightsIndices;
public IrVertex deepClone() {
IrVertex v = new IrVertex();
v.pos = pos != null ? pos.clone() : null;
v.norm = norm != null ? norm.clone() : null;
v.tang4d = tang4d != null ? tang4d.clone() : null;
v.tang = tang != null ? tang.clone() : null;
v.bitang = bitang != null ? bitang.clone() : null;
v.uv0 = uv0 != null ? uv0.clone() : null;
v.uv1 = uv1 != null ? uv1.clone() : null;
v.color = color != null ? color.clone() : null;
v.material = material;
v.smoothing = smoothing;
if (boneWeightsIndices != null) {
v.boneWeightsIndices = new IrBoneWeightIndex[boneWeightsIndices.length];
for (int i = 0; i < boneWeightsIndices.length; i++) {
v.boneWeightsIndices[i] = (IrBoneWeightIndex) boneWeightsIndices[i].clone();
}
}
return v;
}
@Override
public int hashCode() {
int hash = 5;
hash = 73 * hash + (this.pos != null ? this.pos.hashCode() : 0);
hash = 73 * hash + (this.norm != null ? this.norm.hashCode() : 0);
hash = 73 * hash + (this.tang4d != null ? this.tang4d.hashCode() : 0);
hash = 73 * hash + (this.tang != null ? this.tang.hashCode() : 0);
hash = 73 * hash + (this.uv0 != null ? this.uv0.hashCode() : 0);
hash = 73 * hash + (this.uv1 != null ? this.uv1.hashCode() : 0);
hash = 73 * hash + (this.color != null ? this.color.hashCode() : 0);
hash = 73 * hash + (this.material != null ? this.material.hashCode() : 0);
hash = 73 * hash + (this.smoothing != null ? this.smoothing.hashCode() : 0);
hash = 73 * hash + Arrays.deepHashCode(this.boneWeightsIndices);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final IrVertex other = (IrVertex) obj;
if (this.pos != other.pos && (this.pos == null || !this.pos.equals(other.pos))) {
return false;
}
if (this.norm != other.norm && (this.norm == null || !this.norm.equals(other.norm))) {
return false;
}
if (this.tang4d != other.tang4d && (this.tang4d == null || !this.tang4d.equals(other.tang4d))) {
return false;
}
if (this.tang != other.tang && (this.tang == null || !this.tang.equals(other.tang))) {
return false;
}
if (this.uv0 != other.uv0 && (this.uv0 == null || !this.uv0.equals(other.uv0))) {
return false;
}
if (this.uv1 != other.uv1 && (this.uv1 == null || !this.uv1.equals(other.uv1))) {
return false;
}
if (this.color != other.color && (this.color == null || !this.color.equals(other.color))) {
return false;
}
if (this.material != other.material && (this.material == null || !this.material.equals(other.material))) {
return false;
}
if (this.smoothing != other.smoothing && (this.smoothing == null || !this.smoothing.equals(other.smoothing))) {
return false;
}
if (!Arrays.deepEquals(this.boneWeightsIndices, other.boneWeightsIndices)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Vertex { ");
if (pos != null) {
sb.append("pos=").append(pos).append(", ");
}
if (norm != null) {
sb.append("norm=").append(pos).append(", ");
}
if (tang != null) {
sb.append("tang=").append(pos).append(", ");
}
if (uv0 != null) {
sb.append("uv0=").append(pos).append(", ");
}
if (uv1 != null) {
sb.append("uv1=").append(pos).append(", ");
}
if (color != null) {
sb.append("color=").append(pos).append(", ");
}
if (material != null) {
sb.append("material=").append(pos).append(", ");
}
if (smoothing != null) {
sb.append("smoothing=").append(pos).append(", ");
}
if (sb.toString().endsWith(", ")) {
sb.delete(sb.length() - 2, sb.length());
}
sb.append(" }");
return sb.toString();
}
}
Loading…
Cancel
Save