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 animationexperimental
parent
6f29772862
commit
ed2be5e542
@ -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…
Reference in new issue