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