/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/
package com.ardor3d.extension.model.collada.jdom;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdom2.Element;
import com.ardor3d.extension.animation.skeletal.AttachmentPoint;
import com.ardor3d.extension.animation.skeletal.Joint;
import com.ardor3d.extension.animation.skeletal.Skeleton;
import com.ardor3d.extension.model.collada.jdom.data.AssetData;
import com.ardor3d.extension.model.collada.jdom.data.ControllerStore;
import com.ardor3d.extension.model.collada.jdom.data.DataCache;
import com.ardor3d.extension.model.collada.jdom.data.JointNode;
import com.ardor3d.extension.model.collada.jdom.data.NodeType;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Matrix3;
import com.ardor3d.math.Matrix4;
import com.ardor3d.math.Transform;
import com.ardor3d.math.Vector3;
import com.ardor3d.math.Vector4;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.Spatial;
import com.google.common.collect.Lists;
/**
* Methods for parsing Collada data related to scenes and node hierarchy.
*/
public class ColladaNodeUtils {
private static final Logger logger = Logger.getLogger(ColladaNodeUtils.class.getName());
private final DataCache _dataCache;
private final ColladaDOMUtil _colladaDOMUtil;
private final ColladaMaterialUtils _colladaMaterialUtils;
private final ColladaMeshUtils _colladaMeshUtils;
private final ColladaAnimUtils _colladaAnimUtils;
public ColladaNodeUtils(final DataCache dataCache, final ColladaDOMUtil colladaDOMUtil,
final ColladaMaterialUtils colladaMaterialUtils, final ColladaMeshUtils colladaMeshUtils,
final ColladaAnimUtils colladaAnimUtils) {
_dataCache = dataCache;
_colladaDOMUtil = colladaDOMUtil;
_colladaMaterialUtils = colladaMaterialUtils;
_colladaMeshUtils = colladaMeshUtils;
_colladaAnimUtils = colladaAnimUtils;
}
/**
* Retrieves the scene and returns it as an Ardor3D Node.
*
* @param colladaRoot
* The collada root element
* @return Scene as an Node or null if not found
*/
@SuppressWarnings("unchecked")
public Node getVisualScene(final Element colladaRoot) {
if (colladaRoot.getChild("scene") == null) {
logger.warning("No scene found in collada file!");
return null;
}
final Element instance_visual_scene = colladaRoot.getChild("scene").getChild("instance_visual_scene");
if (instance_visual_scene == null) {
logger.warning("No instance_visual_scene found in collada file!");
return null;
}
final Element visualScene = _colladaDOMUtil.findTargetWithId(instance_visual_scene.getAttributeValue("url"));
if (visualScene != null) {
final Node sceneRoot = new Node(
visualScene.getAttributeValue("name") != null ? visualScene.getAttributeValue("name")
: "Collada Root");
// Load each sub node and attach
final JointNode baseJointNode = new JointNode(null);
_dataCache.setRootJointNode(baseJointNode);
for (final Element n : visualScene.getChildren("node")) {
final Node subNode = buildNode(n, baseJointNode);
if (subNode != null) {
sceneRoot.attachChild(subNode);
}
}
// build a list of joints - one list per skeleton - and build a skeleton for each joint list.
for (final JointNode jointChildNode : _dataCache.getRootJointNode().getChildren()) {
final List<Joint> jointList = Lists.newArrayList();
buildJointLists(jointChildNode, jointList);
final Joint[] joints = jointList.toArray(new Joint[jointList.size()]);
final Skeleton skeleton = new Skeleton(joints[0].getName() + "_skeleton", joints);
if (logger.isLoggable(Level.FINE)) {
logger.fine("skeleton created: " + skeleton.getName());
}
for (final Joint joint : jointList) {
_dataCache.getJointSkeletonMapping().put(joint, skeleton);
if (logger.isLoggable(Level.FINE)) {
logger.fine("- Joint " + joint.getName() + " - index: " + joint.getIndex() + " parent index: "
+ joint.getParentIndex());
}
}
_dataCache.addSkeleton(skeleton);
}
// update our world transforms so we can use them to init the default bind matrix of any joints.
sceneRoot.updateWorldTransform(true);
initDefaultJointTransforms(baseJointNode);
// Setup our skinned mesh objects.
for (final ControllerStore controllerStore : _dataCache.getControllers()) {
_colladaMaterialUtils.bindMaterials(controllerStore.instanceController.getChild("bind_material"));
_colladaAnimUtils.buildController(controllerStore.ardorParentNode, controllerStore.instanceController);
_colladaMaterialUtils.unbindMaterials(controllerStore.instanceController.getChild("bind_material"));
}
return sceneRoot;
}
return null;
}
private void buildJointLists(final JointNode jointNode, final List<Joint> jointList) {
final Joint joint = jointNode.getJoint();
joint.setIndex((short) jointList.size());
if (jointNode.getParent().getJoint() != null) {
joint.setParentIndex(jointNode.getParent().getJoint().getIndex());
} else {
joint.setParentIndex(Joint.NO_PARENT);
}
jointList.add(joint);
for (final JointNode jointChildNode : jointNode.getChildren()) {
buildJointLists(jointChildNode, jointList);
}
}
private void initDefaultJointTransforms(final JointNode jointNode) {
final Joint j = jointNode.getJoint();
if (j != null && jointNode.getSceneNode() != null) {
j.setInverseBindPose(jointNode.getSceneNode().getWorldTransform().invert(null));
}
for (final JointNode jointChildNode : jointNode.getChildren()) {
initDefaultJointTransforms(jointChildNode);
}
}
/**
* Parse an asset element into an AssetData object.
*
* @param asset
* @return
*/
@SuppressWarnings("unchecked")
public AssetData parseAsset(final Element asset) {
final AssetData assetData = new AssetData();
for (final Element child : asset.getChildren()) {
if ("contributor".equals(child.getName())) {
parseContributor(assetData, child);
} else if ("created".equals(child.getName())) {
assetData.setCreated(child.getText());
} else if ("keywords".equals(child.getName())) {
assetData.setKeywords(child.getText());
} else if ("modified".equals(child.getName())) {
assetData.setModified(child.getText());
} else if ("revision".equals(child.getName())) {
assetData.setRevision(child.getText());
} else if ("subject".equals(child.getName())) {
assetData.setSubject(child.getText());
} else if ("title".equals(child.getName())) {
assetData.setTitle(child.getText());
} else if ("unit".equals(child.getName())) {
final String name = child.getAttributeValue("name");
if (name != null) {
assetData.setUnitName(name);
}
final String meter = child.getAttributeValue("meter");
if (meter != null) {
assetData.setUnitMeter(Float.parseFloat(meter.replace(",", ".")));
}
} else if ("up_axis".equals(child.getName())) {
final String axis = child.getText();
if ("X_UP".equals(axis)) {
assetData.setUpAxis(new Vector3());
} else if ("Y_UP".equals(axis)) {
assetData.setUpAxis(Vector3.UNIT_Y);
} else if ("Z_UP".equals(axis)) {
assetData.setUpAxis(Vector3.UNIT_Z);
}
}
}
return assetData;
}
@SuppressWarnings("unchecked")
private void parseContributor(final AssetData assetData, final Element contributor) {
for (final Element child : contributor.getChildren()) {
if ("author".equals(child.getName())) {
assetData.setAuthor(child.getText());
} else if ("authoringTool".equals(child.getName())) {
assetData.setCreated(child.getText());
} else if ("comments".equals(child.getName())) {
assetData.setComments(child.getText());
} else if ("copyright".equals(child.getName())) {
assetData.setCopyright(child.getText());
} else if ("source_data".equals(child.getName())) {
assetData.setSourceData(child.getText());
}
}
}
/**
* @param instanceNode
* @return a new Ardor3D node, created from the <node> pointed to by the given <instance_node> element
*/
public Node getNode(final Element instanceNode, final JointNode jointNode) {
final Element node = _colladaDOMUtil.findTargetWithId(instanceNode.getAttributeValue("url"));
if (node == null) {
throw new ColladaException("No node with id: " + instanceNode.getAttributeValue("url") + " found",
instanceNode);
}
return buildNode(node, jointNode);
}
/**
* Recursively parse the node hierarcy.
*
* @param dNode
* @return a new Ardor3D node, created from the given <node> element
*/
@SuppressWarnings("unchecked")
private Node buildNode(final Element dNode, JointNode jointNode) {
final NodeType nodeType = getNodeType(dNode);
final JointNode jointChildNode;
if (nodeType == NodeType.JOINT) {
String name = dNode.getAttributeValue("name");
if (name == null) {
name = dNode.getAttributeValue("id");
}
if (name == null) {
name = dNode.getAttributeValue("sid");
}
final Joint joint = new Joint(name);
jointChildNode = new JointNode(joint);
jointChildNode.setParent(jointNode);
jointNode.getChildren().add(jointChildNode);
jointNode = jointChildNode;
_dataCache.getElementJointMapping().put(dNode, joint);
} else {
jointChildNode = null;
}
String nodeName = dNode.getAttributeValue("name", (String) null);
if (nodeName == null) { // use id if name doesn't exist
nodeName = dNode.getAttributeValue("id", dNode.getName());
}
final Node node = new Node(nodeName);
final List<Element> transforms = new ArrayList<Element>();
for (final Element child : dNode.getChildren()) {
if (_dataCache.getTransformTypes().contains(child.getName())) {
transforms.add(child);
}
}
// process any transform information.
if (!transforms.isEmpty()) {
final Transform localTransform = getNodeTransforms(transforms);
node.setTransform(localTransform);
if (jointChildNode != null) {
jointChildNode.setSceneNode(node);
}
}
// process any instance geometries
for (final Element instance_geometry : dNode.getChildren("instance_geometry")) {
_colladaMaterialUtils.bindMaterials(instance_geometry.getChild("bind_material"));
final Spatial mesh = _colladaMeshUtils.getGeometryMesh(instance_geometry);
if (mesh != null) {
node.attachChild(mesh);
}
_colladaMaterialUtils.unbindMaterials(instance_geometry.getChild("bind_material"));
}
// process any instance controllers
for (final Element instanceController : dNode.getChildren("instance_controller")) {
_dataCache.getControllers().add(new ControllerStore(node, instanceController));
}
// process any instance nodes
for (final Element in : dNode.getChildren("instance_node")) {
final Node subNode = getNode(in, jointNode);
if (subNode != null) {
node.attachChild(subNode);
if (nodeType == NodeType.JOINT
&& getNodeType(_colladaDOMUtil.findTargetWithId(in.getAttributeValue("url"))) == NodeType.NODE) {
// make attachment
createJointAttachment(jointChildNode, node, subNode);
}
}
}
// process any concrete child nodes.
for (final Element n : dNode.getChildren("node")) {
final Node subNode = buildNode(n, jointNode);
if (subNode != null) {
node.attachChild(subNode);
if (nodeType == NodeType.JOINT && getNodeType(n) == NodeType.NODE) {
// make attachment
createJointAttachment(jointChildNode, node, subNode);
}
}
}
// Cache reference
_dataCache.getElementSpatialMapping().put(dNode, node);
return node;
}
protected void createJointAttachment(final JointNode jointChildNode, final Node node, final Node subNode) {
final AttachmentPoint attach = new AttachmentPoint("attach-" + node.getName(), (short) 0, subNode,
new Transform(subNode.getTransform()));
_dataCache.addAttachmentPoint(jointChildNode.getJoint(), attach);
// we will attach to scene instead.
subNode.removeFromParent();
}
private NodeType getNodeType(final Element dNode) {
if (dNode.getAttribute("type") != null) {
return Enum.valueOf(NodeType.class, dNode.getAttributeValue("type"));
} else {
return NodeType.NODE;
}
}
/**
* Combines a list of transform elements into an Ardor3D Transform object.
*
* @param transforms
* List of transform elements
* @return an Ardor3D Transform object
*/
public Transform getNodeTransforms(final List<Element> transforms) {
final Matrix4 workingMat = Matrix4.fetchTempInstance();
final Matrix4 finalMat = Matrix4.fetchTempInstance();
finalMat.setIdentity();
for (final Element transform : transforms) {
final double[] array = _colladaDOMUtil.parseDoubleArray(transform);
if ("translate".equals(transform.getName())) {
workingMat.setIdentity();
workingMat.setColumn(3, new Vector4(array[0], array[1], array[2], 1.0));
finalMat.multiplyLocal(workingMat);
} else if ("rotate".equals(transform.getName())) {
if (array[3] != 0) {
workingMat.setIdentity();
final Matrix3 rotate = new Matrix3().fromAngleAxis(array[3] * MathUtils.DEG_TO_RAD, new Vector3(
array[0], array[1], array[2]));
workingMat.set(rotate);
finalMat.multiplyLocal(workingMat);
}
} else if ("scale".equals(transform.getName())) {
workingMat.setIdentity();
workingMat.scale(new Vector4(array[0], array[1], array[2], 1), workingMat);
finalMat.multiplyLocal(workingMat);
} else if ("matrix".equals(transform.getName())) {
workingMat.fromArray(array);
finalMat.multiplyLocal(workingMat);
} else if ("lookat".equals(transform.getName())) {
final Vector3 pos = new Vector3(array[0], array[1], array[2]);
final Vector3 target = new Vector3(array[3], array[4], array[5]);
final Vector3 up = new Vector3(array[6], array[7], array[8]);
final Matrix3 rot = new Matrix3();
rot.lookAt(target.subtractLocal(pos), up);
workingMat.set(rot);
workingMat.setColumn(3, new Vector4(array[0], array[1], array[2], 1));
finalMat.multiplyLocal(workingMat);
} else {
logger.warning("transform not currently supported: " + transform.getClass().getCanonicalName());
}
}
return new Transform().fromHomogeneousMatrix(finalMat);
}
public void reattachAttachments(final Node scene) {
for (final AttachmentPoint point : _dataCache.getAttachmentPoints().values()) {
scene.attachChild(point.getAttachment());
}
}
}