From ed2be5e54208d8b8fc21987804f025046e950493 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 19 Apr 2015 18:08:28 -0400 Subject: [PATCH] FBX: new FBX importer (not yet enabled - old importer still used by default) Still needs work: * Skeletal animation (many issues with transform hierarchies) * N-gons triangulation (only quads supported at the moment) * Light & Camera importing * Z-up to Y-up correction * Morph animation --- .../com/jme3/scene/plugins/fbx/FbxLoader.java | 413 ++++++++++++ .../scene/plugins/fbx/anim/FbxAnimCurve.java | 144 ++++ .../plugins/fbx/anim/FbxAnimCurveNode.java | 147 +++++ .../scene/plugins/fbx/anim/FbxAnimLayer.java | 82 +++ .../scene/plugins/fbx/anim/FbxAnimStack.java | 111 ++++ .../scene/plugins/fbx/anim/FbxAnimUtil.java | 44 ++ .../scene/plugins/fbx/anim/FbxBindPose.java | 103 +++ .../scene/plugins/fbx/anim/FbxCluster.java | 98 +++ .../scene/plugins/fbx/anim/FbxLimbNode.java | 94 +++ .../plugins/fbx/anim/FbxSkinDeformer.java | 66 ++ .../scene/plugins/fbx/anim/FbxToJmeTrack.java | 202 ++++++ .../scene/plugins/fbx/material/FbxImage.java | 190 ++++++ .../plugins/fbx/material/FbxMaterial.java | 363 +++++++++++ .../fbx/material/FbxMaterialProperties.java | 234 +++++++ .../plugins/fbx/material/FbxTexture.java | 146 +++++ .../jme3/scene/plugins/fbx/mesh/FbxLayer.java | 107 +++ .../plugins/fbx/mesh/FbxLayerElement.java | 243 +++++++ .../jme3/scene/plugins/fbx/mesh/FbxMesh.java | 316 +++++++++ .../scene/plugins/fbx/mesh/FbxMeshUtil.java | 69 ++ .../scene/plugins/fbx/mesh/FbxPolygon.java | 59 ++ .../plugins/fbx/misc/FbxGlobalSettings.java | 145 ++++ .../jme3/scene/plugins/fbx/node/FbxNode.java | 617 ++++++++++++++++++ .../plugins/fbx/node/FbxNodeAttribute.java | 41 ++ .../scene/plugins/fbx/node/FbxNodeUtil.java | 61 ++ .../plugins/fbx/node/FbxNullAttribute.java | 59 ++ .../scene/plugins/fbx/node/FbxRootNode.java | 45 ++ .../jme3/scene/plugins/fbx/obj/FbxObject.java | 144 ++++ .../plugins/fbx/obj/FbxObjectFactory.java | 209 ++++++ .../plugins/fbx/obj/FbxUnknownObject.java | 54 ++ .../jme3/scene/plugins/IrBoneWeightIndex.java | 89 +++ .../java/com/jme3/scene/plugins/IrMesh.java | 46 ++ .../com/jme3/scene/plugins/IrPolygon.java | 46 ++ .../java/com/jme3/scene/plugins/IrUtils.java | 400 ++++++++++++ .../java/com/jme3/scene/plugins/IrVertex.java | 170 +++++ 34 files changed, 5357 insertions(+) create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java create mode 100644 jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java new file mode 100644 index 000000000..fd157cb48 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.fbx; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.Bone; +import com.jme3.animation.BoneTrack; +import com.jme3.animation.Skeleton; +import com.jme3.animation.Track; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.asset.ModelKey; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.fbx.anim.FbxToJmeTrack; +import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; +import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer; +import com.jme3.scene.plugins.fbx.anim.FbxAnimStack; +import com.jme3.scene.plugins.fbx.anim.FbxBindPose; +import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; +import com.jme3.scene.plugins.fbx.file.FbxDump; +import com.jme3.scene.plugins.fbx.file.FbxElement; +import com.jme3.scene.plugins.fbx.file.FbxFile; +import com.jme3.scene.plugins.fbx.file.FbxReader; +import com.jme3.scene.plugins.fbx.file.FbxId; +import com.jme3.scene.plugins.fbx.misc.FbxGlobalSettings; +import com.jme3.scene.plugins.fbx.node.FbxNode; +import com.jme3.scene.plugins.fbx.node.FbxRootNode; +import com.jme3.scene.plugins.fbx.obj.FbxObjectFactory; +import com.jme3.scene.plugins.fbx.obj.FbxObject; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FbxLoader implements AssetLoader { + + private static final Logger logger = Logger.getLogger(FbxLoader.class.getName()); + + private AssetManager assetManager; + + private String sceneName; + private String sceneFilename; + private String sceneFolderName; + private FbxGlobalSettings globalSettings; + private final Map objectMap = new HashMap(); + + private final List animStacks = new ArrayList(); + private final List bindPoses = new ArrayList(); + + @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(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 bindPoseData = bindPose.getJmeObject(); + logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size()); + for (Map.Entry 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 pairs = new HashMap(); + for (FbxAnimStack stack : animStacks) { + for (FbxAnimLayer layer : stack.getLayers()) { + for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) { + for (Map.Entry 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java new file mode 100644 index 000000000..d266dcf95 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java @@ -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); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java new file mode 100644 index 000000000..e8dbe7fc0 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java @@ -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 influencedNodePropertiesMap = new HashMap(); + private final Map propertyToCurveMap = new HashMap(); + private final Map propertyToDefaultMap = new HashMap(); + + 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 getInfluencedNodeProperties() { + return influencedNodePropertiesMap; + } + + public Collection 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); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java new file mode 100644 index 000000000..872626615 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java @@ -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 animCurves = new ArrayList(); + + 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 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); + } +} + \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java new file mode 100644 index 000000000..ea7dbb814 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java @@ -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 getInfluencedNodes() { +// HashSet influencedNodes = new HashSet(); +// 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); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java new file mode 100644 index 000000000..c376757de --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java @@ -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"; +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java new file mode 100644 index 000000000..78d37c74e --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java @@ -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> { + + private final Map bindPose = new HashMap(); + + 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 toJmeObject() { + return bindPose; + } + + @Override + public void connectObject(FbxObject object) { + unsupportedConnectObject(object); + } + + @Override + public void connectObjectProperty(FbxObject object, String property) { + unsupportedConnectObjectProperty(object, property); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java new file mode 100644 index 000000000..3065ce88c --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java @@ -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); + } +} \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java new file mode 100644 index 000000000..4b41b4f47 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java @@ -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 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 bones = new ArrayList(); + + 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java new file mode 100644 index 000000000..7a831cf7f --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java @@ -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> { + + private final List clusters = new ArrayList(); + + public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + protected List 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); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java new file mode 100644 index 000000000..7b7b5ee5b --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java @@ -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 animCurves = new HashMap(); + + public long[] getKeyTimes() { + Set keyFrameTimesSet = new HashSet(); + 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java new file mode 100644 index 000000000..518dd4134 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxImage.java @@ -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); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java new file mode 100644 index 000000000..9be5bf70c --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java @@ -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 { + + 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java new file mode 100644 index 000000000..14d23900d --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java @@ -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 propertyMetaMap = new HashMap(); + + private final Map propertyValueMap = new HashMap(); + + 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; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java new file mode 100644 index 000000000..4569dad33 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java @@ -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 { + + 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); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java new file mode 100644 index 000000000..d1b9d3860 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java @@ -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 references = + new EnumMap(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 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java new file mode 100644 index 000000000..bffd94c65 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayerElement.java @@ -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 (isn't actually defined in FBX) + BoneWeight, // List 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 indexTypes = new HashSet(); + + 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; + } +} + diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java new file mode 100644 index 000000000..5dd911bed --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java @@ -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> { + + 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[] boneIndices; + private ArrayList[] boneWeights; + + private FbxSkinDeformer skinDeformer; + + public FbxMesh(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } + + @Override + public void fromElement(FbxElement element) { + super.fromElement(element); + + List layerElementsList = new ArrayList(); + List layersList = new ArrayList(); + + 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 boneIndicesForVertex = boneIndices[positionIndex]; + ArrayList boneWeightsForVertex = boneWeights[positionIndex]; + + if (boneIndicesForVertex == null) { + boneIndicesForVertex = new ArrayList(); + boneWeightsForVertex = new ArrayList(); + 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 polygonList = new ArrayList(); + + boolean finishPolygon = false; + List vertexIndices = new ArrayList(); + + 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 boneIndices, List 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 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 irMeshes = IrUtils.splitByMaterial(irMesh); + + // Create a jME3 Mesh for each material index. + IntMap jmeMeshes = new IntMap(); + for (IntMap.Entry 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 boneIndicesForVertex = boneIndices[positionIndex]; + ArrayList 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java new file mode 100644 index 000000000..61fb001dd --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java @@ -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; + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java new file mode 100644 index 000000000..bb7773785 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxPolygon.java @@ -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 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 indices) { + FbxPolygon poly = new FbxPolygon(); + poly.indices = listToArray(indices); + return poly; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java new file mode 100644 index 000000000..3a815df6a --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/misc/FbxGlobalSettings.java @@ -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 timeModeToFps = new HashMap(); + + 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: + *
    + *
  • Units are specified in meters.
  • + *
  • Orientation is right-handed with Y-up.
  • + *
+ */ + 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; + } + } + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java new file mode 100644 index 000000000..c0ce06d4b --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java @@ -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 { + + 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: + * jmeChildScale = jmeParentScale / fbxChildScale + */ + NoParentScale + } + + private InheritMode inheritMode = InheritMode.ScaleAfterChildRotation; + + protected FbxNode parent; + protected List children = new ArrayList(); + protected List materials = new ArrayList(); + protected Map userData = new HashMap(); + protected Map> propertyToAnimCurveMap = new HashMap>(); + 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 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 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 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 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 curveNodes = propertyToAnimCurveMap.get(property); + if (curveNodes == null) { + curveNodes = new ArrayList(); + 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); + } + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java new file mode 100644 index 000000000..b63e4bc08 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeAttribute.java @@ -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 extends FbxObject { + public FbxNodeAttribute(AssetManager assetManager, String sceneFolderName) { + super(assetManager, sceneFolderName); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java new file mode 100644 index 000000000..8c35e45e9 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java @@ -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(); + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java new file mode 100644 index 000000000..ce95546d8 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNullAttribute.java @@ -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 { + + 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(); + } + +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java new file mode 100644 index 000000000..83db50283 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxRootNode.java @@ -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 = ""; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java new file mode 100644 index 000000000..d9439a2d4 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObject.java @@ -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 { + + 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); +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java new file mode 100644 index 000000000..ec8c1fd67 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java @@ -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 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 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 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; + } +} diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java new file mode 100644 index 000000000..9a1eaa910 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxUnknownObject.java @@ -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 { + + 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) { + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java new file mode 100644 index 000000000..523993f51 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java @@ -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 { + + 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; + } + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java new file mode 100644 index 000000000..8bb5a6881 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrMesh.java @@ -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; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java new file mode 100644 index 000000000..7bb47cb54 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrPolygon.java @@ -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; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java new file mode 100644 index 000000000..7b20e1670 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java @@ -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 newPolygons = new ArrayList(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 splitByMaterial(IrMesh mesh) { + IntMap> materialToPolyList = new IntMap>(); + 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 polyList = materialToPolyList.get(materialIndex); + if (polyList == null) { + polyList = new ArrayList(); + materialToPolyList.put(materialIndex, polyList); + } + polyList.add(polygon); + } + IntMap materialToMesh = new IntMap(); + for (IntMap.Entry> entry : materialToPolyList) { + int key = entry.getKey(); + List 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 vertexToVertexIndex = new HashMap(); + List vertices = new ArrayList(); + List indexes = new ArrayList(); + + 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; + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java new file mode 100644 index 000000000..5870846a1 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrVertex.java @@ -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(); + } +}