From 9dd02f103042cb8a196f8a3ed2278da443e345bf Mon Sep 17 00:00:00 2001
From: neothemachine
Date: Wed, 5 Dec 2012 17:03:16 +0100
Subject: move all files from trunk to root folder
---
.../model/collada/jdom/ColladaAnimUtils.java | 1174 ++++++++++++++++++++
.../model/collada/jdom/ColladaDOMUtil.java | 378 +++++++
.../model/collada/jdom/ColladaException.java | 31 +
.../model/collada/jdom/ColladaImporter.java | 498 +++++++++
.../model/collada/jdom/ColladaInputPipe.java | 250 +++++
.../model/collada/jdom/ColladaMaterialUtils.java | 588 ++++++++++
.../model/collada/jdom/ColladaMeshUtils.java | 617 ++++++++++
.../model/collada/jdom/ColladaNodeUtils.java | 378 +++++++
.../model/collada/jdom/data/AnimationItem.java | 94 ++
.../model/collada/jdom/data/AssetData.java | 201 ++++
.../model/collada/jdom/data/ColladaStorage.java | 161 +++
.../model/collada/jdom/data/ControllerStore.java | 16 +
.../model/collada/jdom/data/DataCache.java | 229 ++++
.../model/collada/jdom/data/JointNode.java | 54 +
.../model/collada/jdom/data/MaterialInfo.java | 98 ++
.../model/collada/jdom/data/MeshVertPairs.java | 43 +
.../model/collada/jdom/data/NodeType.java | 18 +
.../model/collada/jdom/data/SamplerTypes.java | 80 ++
.../model/collada/jdom/data/SkinData.java | 126 +++
.../model/collada/jdom/data/TransformElement.java | 41 +
.../collada/jdom/plugin/ColladaExtraPlugin.java | 19 +
.../collada/jdom/plugin/GoogleEarthPlugin.java | 46 +
22 files changed, 5140 insertions(+)
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaAnimUtils.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaDOMUtil.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaException.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaImporter.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaInputPipe.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMaterialUtils.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMeshUtils.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaNodeUtils.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AnimationItem.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AssetData.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ColladaStorage.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ControllerStore.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/DataCache.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/JointNode.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MaterialInfo.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MeshVertPairs.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/NodeType.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SamplerTypes.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SkinData.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/TransformElement.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/ColladaExtraPlugin.java
create mode 100644 ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/GoogleEarthPlugin.java
(limited to 'ardor3d-collada/src/main/java/com')
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaAnimUtils.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaAnimUtils.java
new file mode 100644
index 0000000..811c65b
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaAnimUtils.java
@@ -0,0 +1,1174 @@
+/**
+ * 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 .
+ */
+
+package com.ardor3d.extension.model.collada.jdom;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jdom.Attribute;
+import org.jdom.DataConversionException;
+import org.jdom.Element;
+
+import com.ardor3d.extension.animation.skeletal.Joint;
+import com.ardor3d.extension.animation.skeletal.Skeleton;
+import com.ardor3d.extension.animation.skeletal.SkeletonPose;
+import com.ardor3d.extension.animation.skeletal.SkinnedMesh;
+import com.ardor3d.extension.animation.skeletal.clip.AnimationClip;
+import com.ardor3d.extension.animation.skeletal.clip.JointChannel;
+import com.ardor3d.extension.animation.skeletal.clip.TransformChannel;
+import com.ardor3d.extension.model.collada.jdom.ColladaInputPipe.ParamType;
+import com.ardor3d.extension.model.collada.jdom.ColladaInputPipe.Type;
+import com.ardor3d.extension.model.collada.jdom.data.AnimationItem;
+import com.ardor3d.extension.model.collada.jdom.data.ColladaStorage;
+import com.ardor3d.extension.model.collada.jdom.data.DataCache;
+import com.ardor3d.extension.model.collada.jdom.data.MeshVertPairs;
+import com.ardor3d.extension.model.collada.jdom.data.SkinData;
+import com.ardor3d.extension.model.collada.jdom.data.TransformElement;
+import com.ardor3d.extension.model.collada.jdom.data.TransformElement.TransformElementType;
+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.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.AbstractBufferData.VBOAccessMode;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.export.binary.BinaryExporter;
+import com.ardor3d.util.export.binary.BinaryImporter;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.geom.VertMap;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+/**
+ * Methods for parsing Collada data related to animation, skinning and morphing.
+ */
+public class ColladaAnimUtils {
+ private static final Logger logger = Logger.getLogger(ColladaAnimUtils.class.getName());
+
+ private final ColladaStorage _colladaStorage;
+ private final DataCache _dataCache;
+ private final ColladaDOMUtil _colladaDOMUtil;
+ private final ColladaMeshUtils _colladaMeshUtils;
+
+ public ColladaAnimUtils(final ColladaStorage colladaStorage, final DataCache dataCache,
+ final ColladaDOMUtil colladaDOMUtil, final ColladaMeshUtils colladaMeshUtils) {
+ _colladaStorage = colladaStorage;
+ _dataCache = dataCache;
+ _colladaDOMUtil = colladaDOMUtil;
+ _colladaMeshUtils = colladaMeshUtils;
+ }
+
+ /**
+ * Retrieve a name to use for the skin node based on the element names.
+ *
+ * @param ic
+ * instance_controller element.
+ * @param controller
+ * controller element
+ * @return name.
+ * @see SkinData#SkinData(String)
+ */
+ private String getSkinStoreName(final Element ic, final Element controller) {
+ final String controllerName = controller.getAttributeValue("name", (String) null) != null ? controller
+ .getAttributeValue("name", (String) null) : controller.getAttributeValue("id", (String) null);
+ final String instanceControllerName = ic.getAttributeValue("name", (String) null) != null ? ic
+ .getAttributeValue("name", (String) null) : ic.getAttributeValue("sid", (String) null);
+ final String storeName = (controllerName != null ? controllerName : "")
+ + (controllerName != null && instanceControllerName != null ? " : " : "")
+ + (instanceControllerName != null ? instanceControllerName : "");
+ return storeName;
+ }
+
+ /**
+ * Copy the render states from our source Spatial to the destination Spatial. Does not recurse.
+ *
+ * @param source
+ * @param target
+ */
+ private void copyRenderStates(final Spatial source, final Spatial target) {
+ final EnumMap states = source.getLocalRenderStates();
+ for (final RenderState state : states.values()) {
+ target.setRenderState(state);
+ }
+ }
+
+ /**
+ * Clone the given MeshData object via deep copy using the Ardor3D BinaryExporter and BinaryImporter.
+ *
+ * @param meshData
+ * the source to clone.
+ * @return the clone.
+ * @throws IOException
+ * if we have troubles during the clone.
+ */
+ private MeshData copyMeshData(final MeshData meshData) throws IOException {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ final BinaryExporter exporter = new BinaryExporter();
+ exporter.save(meshData, bos);
+ bos.flush();
+ final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+ final BinaryImporter importer = new BinaryImporter();
+ final Savable sav = importer.load(bis);
+ return (MeshData) sav;
+ }
+
+ /**
+ * Builds data based on an instance controller element.
+ *
+ * @param node
+ * Ardor3D parent Node
+ * @param instanceController
+ */
+ void buildController(final Node node, final Element instanceController) {
+ final Element controller = _colladaDOMUtil.findTargetWithId(instanceController.getAttributeValue("url"));
+
+ if (controller == null) {
+ throw new ColladaException("Unable to find controller with id: "
+ + instanceController.getAttributeValue("url"), instanceController);
+ }
+
+ final Element skin = controller.getChild("skin");
+ if (skin != null) {
+ buildSkinMeshes(node, instanceController, controller, skin);
+ } else {
+ // look for morph... can only be one or the other according to Collada
+ final Element morph = controller.getChild("morph");
+ if (morph != null) {
+ buildMorphMeshes(node, controller, morph);
+ }
+ }
+ }
+
+ /**
+ * Construct skin mesh(es) from the skin element and attach them (under a single new Node) to the given parent Node.
+ *
+ * @param ardorParentNode
+ * Ardor3D Node to attach our skin node to.
+ * @param instanceController
+ * the element. We'll parse the skeleton reference from here.
+ * @param controller
+ * the referenced element. Used for naming purposes.
+ * @param skin
+ * our element.
+ */
+ @SuppressWarnings("unchecked")
+ private void buildSkinMeshes(final Node ardorParentNode, final Element instanceController,
+ final Element controller, final Element skin) {
+ final String skinSource = skin.getAttributeValue("source");
+
+ final Element skinNodeEL = _colladaDOMUtil.findTargetWithId(skinSource);
+ if (skinNodeEL == null || !"geometry".equals(skinNodeEL.getName())) {
+ throw new ColladaException("Expected a mesh for skin source with url: " + skinSource + " got instead: "
+ + skinNodeEL, skin);
+ }
+
+ final Element geometry = skinNodeEL;
+
+ final Node meshNode = _colladaMeshUtils.buildMesh(geometry);
+ if (meshNode != null) {
+ // Look for skeleton entries in the original element
+ final List skeletonRoots = Lists.newArrayList();
+ for (final Element sk : (List) instanceController.getChildren("skeleton")) {
+ final Element skroot = _colladaDOMUtil.findTargetWithId(sk.getText());
+ if (skroot != null) {
+ // add as a possible root for when we need to locate a joint by name later.
+ skeletonRoots.add(skroot);
+ } else {
+ throw new ColladaException("Unable to find node with id: " + sk.getText()
+ + ", referenced from skeleton " + sk, sk);
+ }
+ }
+
+ // Read in our joints node
+ final Element jointsEL = skin.getChild("joints");
+ if (jointsEL == null) {
+ throw new ColladaException("skin found without joints.", skin);
+ }
+
+ // Pull out our joint names and bind matrices
+ final List jointNames = Lists.newArrayList();
+ final List bindMatrices = Lists.newArrayList();
+ final List paramTypes = Lists.newArrayList();
+
+ for (final Element inputEL : (List) jointsEL.getChildren("input")) {
+ final ColladaInputPipe pipe = new ColladaInputPipe(_colladaDOMUtil, inputEL);
+ final ColladaInputPipe.SourceData sd = pipe.getSourceData();
+ if (pipe.getType() == ColladaInputPipe.Type.JOINT) {
+ final String[] namesData = sd.stringArray;
+ for (int i = sd.offset; i < namesData.length; i += sd.stride) {
+ jointNames.add(namesData[i]);
+ paramTypes.add(sd.paramType);
+ }
+ } else if (pipe.getType() == ColladaInputPipe.Type.INV_BIND_MATRIX) {
+ final float[] floatData = sd.floatArray;
+ final FloatBuffer source = BufferUtils.createFloatBufferOnHeap(16);
+ for (int i = sd.offset; i < floatData.length; i += sd.stride) {
+ source.rewind();
+ source.put(floatData, i, 16);
+ source.flip();
+ final Matrix4 mat = new Matrix4().fromFloatBuffer(source);
+ bindMatrices.add(new Transform().fromHomogeneousMatrix(mat));
+ }
+ }
+ }
+
+ // Use the skeleton information from the instance_controller to set the parent array locations on the
+ // joints.
+ Skeleton ourSkeleton = null; // TODO: maybe not the best way. iterate
+ final int[] order = new int[jointNames.size()];
+ for (int i = 0; i < jointNames.size(); i++) {
+ final String name = jointNames.get(i);
+ final ParamType paramType = paramTypes.get(i);
+ final String searcher = paramType == ParamType.idref_param ? "id" : "sid";
+ Element found = null;
+ for (final Element root : skeletonRoots) {
+ if (name.equals(root.getAttributeValue(searcher))) {
+ found = root;
+ } else if (paramType == ParamType.idref_param) {
+ found = _colladaDOMUtil.findTargetWithId(name);
+ } else {
+ found = (Element) _colladaDOMUtil.selectSingleNode(root, ".//*[@sid='" + name + "']");
+ }
+
+ // Last resorts (bad exporters)
+ if (found == null) {
+ found = _colladaDOMUtil.findTargetWithId(name);
+ }
+ if (found == null) {
+ found = (Element) _colladaDOMUtil.selectSingleNode(root, ".//*[@name='" + name + "']");
+ }
+
+ if (found != null) {
+ break;
+ }
+ }
+ if (found == null) {
+ if (paramType == ParamType.idref_param) {
+ found = _colladaDOMUtil.findTargetWithId(name);
+ } else {
+ found = (Element) _colladaDOMUtil.selectSingleNode(geometry, "/*//visual_scene//*[@sid='"
+ + name + "']");
+ }
+
+ // Last resorts (bad exporters)
+ if (found == null) {
+ found = _colladaDOMUtil.findTargetWithId(name);
+ }
+ if (found == null) {
+ found = (Element) _colladaDOMUtil.selectSingleNode(geometry, "/*//visual_scene//*[@name='"
+ + name + "']");
+ }
+
+ if (found == null) {
+ throw new ColladaException("Unable to find joint with " + searcher + ": " + name, skin);
+ }
+ }
+
+ final Joint joint = _dataCache.getElementJointMapping().get(found);
+ if (joint == null) {
+ logger.warning("unable to parse joint for: " + found.getName() + " " + name);
+ return;
+ }
+ joint.setInverseBindPose(bindMatrices.get(i));
+
+ ourSkeleton = _dataCache.getJointSkeletonMapping().get(joint);
+ order[i] = joint.getIndex();
+ }
+
+ // Make our skeleton pose
+ SkeletonPose skPose = _dataCache.getSkeletonPoseMapping().get(ourSkeleton);
+ if (skPose == null) {
+ skPose = new SkeletonPose(ourSkeleton);
+ _dataCache.getSkeletonPoseMapping().put(ourSkeleton, skPose);
+
+ // Skeleton's default to bind position, so update the global transforms.
+ skPose.updateTransforms();
+ }
+
+ // Read in our vertex_weights node
+ final Element weightsEL = skin.getChild("vertex_weights");
+ if (weightsEL == null) {
+ throw new ColladaException("skin found without vertex_weights.", skin);
+ }
+
+ // Pull out our per vertex joint indices and weights
+ final List jointIndices = Lists.newArrayList();
+ final List jointWeights = Lists.newArrayList();
+ int indOff = 0, weightOff = 0;
+
+ int maxOffset = 0;
+ for (final Element inputEL : (List) weightsEL.getChildren("input")) {
+ final ColladaInputPipe pipe = new ColladaInputPipe(_colladaDOMUtil, inputEL);
+ final ColladaInputPipe.SourceData sd = pipe.getSourceData();
+ if (pipe.getOffset() > maxOffset) {
+ maxOffset = pipe.getOffset();
+ }
+ if (pipe.getType() == ColladaInputPipe.Type.JOINT) {
+ indOff = pipe.getOffset();
+ final String[] namesData = sd.stringArray;
+ for (int i = sd.offset; i < namesData.length; i += sd.stride) {
+ // XXX: the Collada spec says this could be -1?
+ final String name = namesData[i];
+ final int index = jointNames.indexOf(name);
+ if (index >= 0) {
+ jointIndices.add((short) index);
+ } else {
+ throw new ColladaException("Unknown joint accessed: " + name, inputEL);
+ }
+ }
+ } else if (pipe.getType() == ColladaInputPipe.Type.WEIGHT) {
+ weightOff = pipe.getOffset();
+ final float[] floatData = sd.floatArray;
+ for (int i = sd.offset; i < floatData.length; i += sd.stride) {
+ jointWeights.add(floatData[i]);
+ }
+ }
+ }
+
+ // Pull our values array
+ int firstIndex = 0, count = 0;
+ final int[] vals = _colladaDOMUtil.parseIntArray(weightsEL.getChild("v"));
+ try {
+ count = weightsEL.getAttribute("count").getIntValue();
+ } catch (final DataConversionException e) {
+ throw new ColladaException("Unable to parse count attribute.", weightsEL);
+ }
+ // use the vals to fill our vert weight map
+ final int[][] vertWeightMap = new int[count][];
+ int index = 0;
+ for (final int length : _colladaDOMUtil.parseIntArray(weightsEL.getChild("vcount"))) {
+ final int[] entry = new int[(maxOffset + 1) * length];
+ vertWeightMap[index++] = entry;
+
+ System.arraycopy(vals, (maxOffset + 1) * firstIndex, entry, 0, entry.length);
+
+ firstIndex += length;
+ }
+
+ // Create a record for the global ColladaStorage.
+ final String storeName = getSkinStoreName(instanceController, controller);
+ final SkinData skinDataStore = new SkinData(storeName);
+ // add pose to store
+ skinDataStore.setPose(skPose);
+
+ // Create a base Node for our skin meshes
+ final Node skinNode = new Node(meshNode.getName());
+ // copy Node render states across.
+ copyRenderStates(meshNode, skinNode);
+ // add node to store
+ skinDataStore.setSkinBaseNode(skinNode);
+
+ // Grab the bind_shape_matrix from skin
+ final Element bindShapeMatrixEL = skin.getChild("bind_shape_matrix");
+ final Transform bindShapeMatrix = new Transform();
+ if (bindShapeMatrixEL != null) {
+ final double[] array = _colladaDOMUtil.parseDoubleArray(bindShapeMatrixEL);
+ bindShapeMatrix.fromHomogeneousMatrix(new Matrix4().fromArray(array));
+ }
+
+ // Visit our Node and pull out any Mesh children. Turn them into SkinnedMeshes
+ for (final Spatial spat : meshNode.getChildren()) {
+ if (spat instanceof Mesh && ((Mesh) spat).getMeshData().getVertexCount() > 0) {
+ final Mesh sourceMesh = (Mesh) spat;
+ final SkinnedMesh skMesh = new SkinnedMesh(sourceMesh.getName());
+ skMesh.setCurrentPose(skPose);
+
+ // copy material info mapping for later use
+ final String material = _dataCache.getMeshMaterialMap().get(sourceMesh);
+ _dataCache.getMeshMaterialMap().put(skMesh, material);
+
+ // copy mesh render states across.
+ copyRenderStates(sourceMesh, skMesh);
+
+ // copy hints across
+ skMesh.getSceneHints().set(sourceMesh.getSceneHints());
+
+ try {
+ // Use source mesh as bind pose data in the new SkinnedMesh
+ final MeshData bindPose = copyMeshData(sourceMesh.getMeshData());
+ skMesh.setBindPoseData(bindPose);
+
+ // Apply our BSM
+ if (!bindShapeMatrix.isIdentity()) {
+ bindPose.transformVertices(bindShapeMatrix);
+ if (bindPose.getNormalBuffer() != null) {
+ bindPose.transformNormals(bindShapeMatrix, true);
+ }
+ }
+
+ // TODO: This is only needed for CPU skinning... consider a way of making it optional.
+ // Copy bind pose to mesh data to setup for CPU skinning
+ final MeshData meshData = copyMeshData(skMesh.getBindPoseData());
+ meshData.getVertexCoords().setVboAccessMode(VBOAccessMode.StreamDraw);
+ if (meshData.getNormalCoords() != null) {
+ meshData.getNormalCoords().setVboAccessMode(VBOAccessMode.StreamDraw);
+ }
+ skMesh.setMeshData(meshData);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ throw new ColladaException("Unable to copy skeleton bind pose data.", geometry);
+ }
+
+ // Grab the MeshVertPairs from Global for this mesh.
+ final Collection vertPairsList = _dataCache.getVertMappings().get(geometry);
+ MeshVertPairs pairsMap = null;
+ if (vertPairsList != null) {
+ for (final MeshVertPairs pairs : vertPairsList) {
+ if (pairs.getMesh() == sourceMesh) {
+ pairsMap = pairs;
+ break;
+ }
+ }
+ }
+
+ if (pairsMap == null) {
+ throw new ColladaException("Unable to locate pair map for geometry.", geometry);
+ }
+
+ // Check for a remapping, if we optimized geometry
+ final VertMap vertMap = _dataCache.getMeshVertMap().get(sourceMesh);
+
+ // Use pairs map and vertWeightMap to build our weights and joint indices.
+ {
+ // count number of weights used
+ int maxWeightsPerVert = 0;
+ int weightCount;
+ for (final int originalIndex : pairsMap.getIndices()) {
+ weightCount = 0;
+
+ // get weights and joints at original index and add weights up to get divisor sum
+ // we'll assume 0's for vertices with no matching weight.
+ if (vertWeightMap.length > originalIndex) {
+ final int[] data = vertWeightMap[originalIndex];
+ for (int i = 0; i < data.length; i += maxOffset + 1) {
+ final float weight = jointWeights.get(data[i + weightOff]);
+ if (weight != 0) {
+ weightCount++;
+ }
+ }
+ if (weightCount > maxWeightsPerVert) {
+ maxWeightsPerVert = weightCount;
+ }
+ }
+ }
+
+ final int verts = skMesh.getMeshData().getVertexCount();
+ final FloatBuffer weightBuffer = BufferUtils.createFloatBuffer(verts * maxWeightsPerVert);
+ final ShortBuffer jointIndexBuffer = BufferUtils.createShortBuffer(verts * maxWeightsPerVert);
+ int j;
+ float sum = 0;
+ final float[] weights = new float[maxWeightsPerVert];
+ final short[] indices = new short[maxWeightsPerVert];
+ int originalIndex;
+ for (int x = 0; x < verts; x++) {
+ if (vertMap != null) {
+ originalIndex = pairsMap.getIndices()[vertMap.getFirstOldIndex(x)];
+ } else {
+ originalIndex = pairsMap.getIndices()[x];
+ }
+
+ j = 0;
+ sum = 0;
+
+ // get weights and joints at original index and add weights up to get divisor sum
+ // we'll assume 0's for vertices with no matching weight.
+ if (vertWeightMap.length > originalIndex) {
+ final int[] data = vertWeightMap[originalIndex];
+ for (int i = 0; i < data.length; i += maxOffset + 1) {
+ final float weight = jointWeights.get(data[i + weightOff]);
+ if (weight != 0) {
+ weights[j] = jointWeights.get(data[i + weightOff]);
+ indices[j] = (short) order[jointIndices.get(data[i + indOff])];
+ sum += weights[j++];
+ }
+ }
+ }
+ // add extra padding as needed
+ while (j < maxWeightsPerVert) {
+ weights[j] = 0;
+ indices[j++] = 0;
+ }
+ // add weights to weightBuffer / sum
+ for (final float w : weights) {
+ weightBuffer.put(sum != 0 ? w / sum : 0);
+ }
+ // add joint indices to jointIndexBuffer
+ jointIndexBuffer.put(indices);
+ }
+
+ final float[] totalWeights = new float[weightBuffer.capacity()];
+ weightBuffer.flip();
+ weightBuffer.get(totalWeights);
+ skMesh.setWeights(totalWeights);
+
+ final short[] totalIndices = new short[jointIndexBuffer.capacity()];
+ jointIndexBuffer.flip();
+ jointIndexBuffer.get(totalIndices);
+ skMesh.setJointIndices(totalIndices);
+
+ skMesh.setWeightsPerVert(maxWeightsPerVert);
+ }
+
+ // add to the skinNode.
+ skinNode.attachChild(skMesh);
+
+ // Manually apply our bind pose to the skin mesh.
+ skMesh.applyPose();
+
+ // Update the model bounding.
+ skMesh.updateModelBound();
+
+ // add mesh to store
+ skinDataStore.getSkins().add(skMesh);
+ }
+ }
+
+ // add to Node
+ ardorParentNode.attachChild(skinNode);
+
+ // Add skin record to storage.
+ _colladaStorage.getSkins().add(skinDataStore);
+ }
+ }
+
+ /**
+ * Construct morph mesh(es) from the element and attach them (under a single new Node) to the given parent
+ * Node.
+ *
+ * Note: This method current does not do anything but attach the referenced mesh since Ardor3D does not yet support
+ * morph target animation.
+ *
+ * @param ardorParentNode
+ * Ardor3D Node to attach our morph mesh to.
+ * @param controller
+ * the referenced element. Used for naming purposes.
+ * @param morph
+ * our element
+ */
+ private void buildMorphMeshes(final Node ardorParentNode, final Element controller, final Element morph) {
+ final String skinSource = morph.getAttributeValue("source");
+
+ final Element skinNode = _colladaDOMUtil.findTargetWithId(skinSource);
+ if (skinNode == null || !"geometry".equals(skinNode.getName())) {
+ throw new ColladaException("Expected a mesh for morph source with url: " + skinSource
+ + " (line number is referring morph)", morph);
+ }
+
+ final Element geometry = skinNode;
+
+ final Spatial baseMesh = _colladaMeshUtils.buildMesh(geometry);
+
+ // TODO: support morph animations someday.
+ if (logger.isLoggable(Level.WARNING)) {
+ logger.warning("Morph target animation not yet supported.");
+ }
+
+ // Just add mesh.
+ if (baseMesh != null) {
+ ardorParentNode.attachChild(baseMesh);
+ }
+ }
+
+ /**
+ * Parse all animations in library_animations
+ *
+ * @param colladaRoot
+ */
+ public void parseLibraryAnimations(final Element colladaRoot) {
+ final Element libraryAnimations = colladaRoot.getChild("library_animations");
+
+ if (libraryAnimations == null || libraryAnimations.getChildren().isEmpty()) {
+ if (logger.isLoggable(Level.WARNING)) {
+ logger.warning("No animations found in collada file!");
+ }
+ return;
+ }
+
+ final AnimationItem animationItemRoot = new AnimationItem("Animation Root");
+ _colladaStorage.setAnimationItemRoot(animationItemRoot);
+
+ final Multimap channelMap = ArrayListMultimap.create();
+
+ parseAnimations(channelMap, libraryAnimations, animationItemRoot);
+
+ for (final Element key : channelMap.keySet()) {
+ buildAnimations(key, channelMap.get(key));
+ }
+ }
+
+ /**
+ * Merge all animation channels into Ardor jointchannels
+ *
+ * @param entry
+ */
+ @SuppressWarnings("unchecked")
+ private void buildAnimations(final Element parentElement, final Collection targetList) {
+
+ final List elementTransforms = new ArrayList();
+ for (final Element child : (List) parentElement.getChildren()) {
+ if (_dataCache.getTransformTypes().contains(child.getName())) {
+ elementTransforms.add(child);
+ }
+ }
+ final List transformList = getNodeTransformList(elementTransforms);
+
+ AnimationItem animationItemRoot = null;
+ for (final TargetChannel targetChannel : targetList) {
+ if (animationItemRoot == null) {
+ animationItemRoot = targetChannel.animationItemRoot;
+ }
+ final String source = targetChannel.source;
+ // final Target target = targetChannel.target;
+ final Element targetNode = targetChannel.targetNode;
+
+ final int targetIndex = elementTransforms.indexOf(targetNode);
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine(parentElement.getName() + "(" + parentElement.getAttributeValue("name") + ") -> "
+ + targetNode.getName() + "(" + targetIndex + ")");
+ }
+
+ final EnumMap pipes = Maps.newEnumMap(Type.class);
+
+ final Element samplerElement = _colladaDOMUtil.findTargetWithId(source);
+ for (final Element inputElement : (List) samplerElement.getChildren("input")) {
+ final ColladaInputPipe pipe = new ColladaInputPipe(_colladaDOMUtil, inputElement);
+ pipes.put(pipe.getType(), pipe);
+ }
+
+ // get input (which is TIME for now)
+ final ColladaInputPipe inputPipe = pipes.get(Type.INPUT);
+ final ColladaInputPipe.SourceData sdIn = inputPipe.getSourceData();
+ final float[] time = sdIn.floatArray;
+ targetChannel.time = time;
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("inputPipe: " + Arrays.toString(time));
+ }
+
+ // get output data
+ final ColladaInputPipe outputPipe = pipes.get(Type.OUTPUT);
+ final ColladaInputPipe.SourceData sdOut = outputPipe.getSourceData();
+ final float[] animationData = sdOut.floatArray;
+ targetChannel.animationData = animationData;
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("outputPipe: " + Arrays.toString(animationData));
+ }
+
+ // TODO: Need to add support for other interpolation types.
+
+ // get target array from transform list
+ final TransformElement transformElement = transformList.get(targetIndex);
+ final double[] array = transformElement.getArray();
+ targetChannel.array = array;
+
+ final int stride = sdOut.stride;
+ targetChannel.stride = stride;
+
+ targetChannel.currentPos = 0;
+ }
+
+ final List finalTimeList = Lists.newArrayList();
+ final List finalTransformList = Lists.newArrayList();
+ final List workingChannels = Lists.newArrayList();
+ for (;;) {
+ float lowestTime = Float.MAX_VALUE;
+ boolean found = false;
+ for (final TargetChannel targetChannel : targetList) {
+ if (targetChannel.currentPos < targetChannel.time.length) {
+ final float time = targetChannel.time[targetChannel.currentPos];
+ if (time < lowestTime) {
+ lowestTime = time;
+ }
+ found = true;
+ }
+ }
+ if (!found) {
+ break;
+ }
+
+ workingChannels.clear();
+ for (final TargetChannel targetChannel : targetList) {
+ if (targetChannel.currentPos < targetChannel.time.length) {
+ final float time = targetChannel.time[targetChannel.currentPos];
+ if (time == lowestTime) {
+ workingChannels.add(targetChannel);
+ }
+ }
+ }
+
+ for (final TargetChannel targetChannel : workingChannels) {
+ final Target target = targetChannel.target;
+ final float[] animationData = targetChannel.animationData;
+ final double[] array = targetChannel.array;
+
+ // set the correct values depending on accessor
+ final int position = targetChannel.currentPos * targetChannel.stride;
+ if (target.accessorType == AccessorType.None) {
+ for (int j = 0; j < array.length; j++) {
+ array[j] = animationData[position + j];
+ }
+ } else {
+ if (target.accessorType == AccessorType.Vector) {
+ array[target.accessorIndexX] = animationData[position];
+ } else if (target.accessorType == AccessorType.Matrix) {
+ array[target.accessorIndexY * 4 + target.accessorIndexX] = animationData[position];
+ }
+ }
+ targetChannel.currentPos++;
+ }
+
+ // bake the transform
+ final Transform transform = bakeTransforms(transformList);
+ finalTimeList.add(lowestTime);
+ finalTransformList.add(transform);
+ }
+
+ final float[] time = new float[finalTimeList.size()];
+ for (int i = 0; i < finalTimeList.size(); i++) {
+ time[i] = finalTimeList.get(i);
+ }
+ final Transform[] transforms = finalTransformList.toArray(new Transform[finalTransformList.size()]);
+
+ AnimationClip animationClip = animationItemRoot.getAnimationClip();
+ if (animationClip == null) {
+ animationClip = new AnimationClip(animationItemRoot.getName());
+ animationItemRoot.setAnimationClip(animationClip);
+ }
+
+ // Make an animation channel - first find if we have a matching joint
+ Joint joint = _dataCache.getElementJointMapping().get(parentElement);
+ if (joint == null) {
+ String nodeName = parentElement.getAttributeValue("name", (String) null);
+ if (nodeName == null) { // use id if name doesn't exist
+ nodeName = parentElement.getAttributeValue("id", parentElement.getName());
+ }
+ if (nodeName != null) {
+ joint = _dataCache.getExternalJointMapping().get(nodeName);
+ }
+
+ if (joint == null) {
+ // no joint still, so make a transform channel.
+ final TransformChannel transformChannel = new TransformChannel(nodeName, time, transforms);
+ animationClip.addChannel(transformChannel);
+ _colladaStorage.getAnimationChannels().add(transformChannel);
+ return;
+ }
+ }
+
+ // create joint channel
+ final JointChannel jointChannel = new JointChannel(joint, time, transforms);
+ animationClip.addChannel(jointChannel);
+ _colladaStorage.getAnimationChannels().add(jointChannel);
+ }
+
+ /**
+ * Stores animation data to use for merging into jointchannels.
+ */
+ private static class TargetChannel {
+ Target target;
+ Element targetNode;
+ String source;
+ AnimationItem animationItemRoot;
+
+ float[] time;
+ float[] animationData;
+ double[] array;
+ int stride;
+ int currentPos;
+
+ public TargetChannel(final Target target, final Element targetNode, final String source,
+ final AnimationItem animationItemRoot) {
+ this.target = target;
+ this.targetNode = targetNode;
+ this.source = source;
+ this.animationItemRoot = animationItemRoot;
+ }
+ }
+
+ /**
+ * Gather up all animation channels based on what nodes they affect.
+ *
+ * @param channelMap
+ * @param animationRoot
+ * @param animationItemRoot
+ */
+ @SuppressWarnings("unchecked")
+ private void parseAnimations(final Multimap channelMap, final Element animationRoot,
+ final AnimationItem animationItemRoot) {
+ if (animationRoot.getChild("animation") != null) {
+ Attribute nameAttribute = animationRoot.getAttribute("name");
+ if (nameAttribute == null) {
+ nameAttribute = animationRoot.getAttribute("id");
+ }
+ final String name = nameAttribute != null ? nameAttribute.getValue() : "Default";
+
+ final AnimationItem animationItem = new AnimationItem(name);
+ animationItemRoot.getChildren().add(animationItem);
+
+ for (final Element animationElement : (List) animationRoot.getChildren("animation")) {
+ parseAnimations(channelMap, animationElement, animationItem);
+ }
+ }
+ if (animationRoot.getChild("channel") != null) {
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("\n-- Parsing animation channels --");
+ }
+ final List channels = animationRoot.getChildren("channel");
+ for (final Element channel : channels) {
+ final String source = channel.getAttributeValue("source");
+
+ final String targetString = channel.getAttributeValue("target");
+ if (targetString == null || targetString.isEmpty()) {
+ return;
+ }
+
+ final Target target = processTargetString(targetString);
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("channel source: " + target.toString());
+ }
+ final Element targetNode = findTargetNode(target);
+ if (targetNode == null || !_dataCache.getTransformTypes().contains(targetNode.getName())) {
+ // TODO: pass with warning or exception or nothing?
+ // throw new ColladaException("No target transform node found for target: " + target, target);
+ continue;
+ }
+ if ("rotate".equals(targetNode.getName())) {
+ target.accessorType = AccessorType.Vector;
+ target.accessorIndexX = 3;
+ }
+
+ channelMap.put(targetNode.getParentElement(), new TargetChannel(target, targetNode, source,
+ animationItemRoot));
+ }
+ }
+ }
+
+ /**
+ * Find a target node based on collada target format.
+ *
+ * @param target
+ * @return
+ */
+ private Element findTargetNode(final Target target) {
+ Element currentElement = _colladaDOMUtil.findTargetWithId(target.id);
+ if (currentElement == null) {
+ throw new ColladaException("No target found with id: " + target.id, target);
+ }
+
+ for (final String sid : target.sids) {
+ final String query = ".//*[@sid='" + sid + "']";
+ final Element sidElement = (Element) _colladaDOMUtil.selectSingleNode(currentElement, query);
+ if (sidElement == null) {
+ // throw new ColladaException("No element found with sid: " + sid, target);
+
+ // TODO: this is a hack to support older 3ds max exports. will be removed and instead use
+ // the above exception
+ // logger.warning("No element found with sid: " + sid + ", trying with first child.");
+ // final List children = currentElement.getChildren();
+ // if (!children.isEmpty()) {
+ // currentElement = children.get(0);
+ // }
+ // break;
+
+ if (logger.isLoggable(Level.WARNING)) {
+ logger.warning("No element found with sid: " + sid + ", skipping channel.");
+ }
+ return null;
+ } else {
+ currentElement = sidElement;
+ }
+ }
+
+ return currentElement;
+ }
+
+ private static final Map symbolMap = Maps.newHashMap();
+ static {
+ symbolMap.put("ANGLE", 3);
+ symbolMap.put("TIME", 0);
+
+ symbolMap.put("X", 0);
+ symbolMap.put("Y", 1);
+ symbolMap.put("Z", 2);
+ symbolMap.put("W", 3);
+
+ symbolMap.put("R", 0);
+ symbolMap.put("G", 1);
+ symbolMap.put("B", 2);
+ symbolMap.put("A", 3);
+
+ symbolMap.put("S", 0);
+ symbolMap.put("T", 1);
+ symbolMap.put("P", 2);
+ symbolMap.put("Q", 3);
+
+ symbolMap.put("U", 0);
+ symbolMap.put("V", 1);
+ symbolMap.put("P", 2);
+ symbolMap.put("Q", 3);
+ }
+
+ /**
+ * Break up a target uri string into id, sids and accessors
+ *
+ * @param targetString
+ * @return
+ */
+ private Target processTargetString(final String targetString) {
+ final Target target = new Target();
+
+ int accessorIndex = targetString.indexOf(".");
+ if (accessorIndex == -1) {
+ accessorIndex = targetString.indexOf("(");
+ }
+ final boolean hasAccessor = accessorIndex != -1;
+ if (accessorIndex == -1) {
+ accessorIndex = targetString.length();
+ }
+
+ final String baseString = targetString.substring(0, accessorIndex);
+
+ int sidIndex = baseString.indexOf("/");
+ final boolean hasSid = sidIndex != -1;
+ if (!hasSid) {
+ sidIndex = baseString.length();
+ }
+
+ final String id = baseString.substring(0, sidIndex);
+ target.id = id;
+
+ if (hasSid) {
+ final String sidGroup = baseString.substring(sidIndex + 1, baseString.length());
+
+ final StringTokenizer tokenizer = new StringTokenizer(sidGroup, "/");
+ while (tokenizer.hasMoreTokens()) {
+ final String sid = tokenizer.nextToken();
+ target.sids.add(sid);
+ }
+ }
+
+ if (hasAccessor) {
+ String accessorString = targetString.substring(accessorIndex, targetString.length());
+ accessorString = accessorString.replace(".", "");
+
+ if (accessorString.startsWith("(")) {
+ int endPara = accessorString.indexOf(")");
+ final String indexXString = accessorString.substring(1, endPara);
+ target.accessorIndexX = Integer.parseInt(indexXString);
+ if (endPara < accessorString.length() - 1) {
+ final String lastAccessorString = accessorString.substring(endPara + 1, accessorString.length());
+ endPara = lastAccessorString.indexOf(")");
+ final String indexYString = lastAccessorString.substring(1, endPara);
+ target.accessorIndexY = Integer.parseInt(indexYString);
+ target.accessorType = AccessorType.Matrix;
+ } else {
+ target.accessorType = AccessorType.Vector;
+ }
+ } else {
+ target.accessorIndexX = symbolMap.get(accessorString);
+ target.accessorType = AccessorType.Vector;
+ }
+ }
+
+ return target;
+ }
+
+ /**
+ * Convert a list of collada elements into a list of TransformElements
+ *
+ * @param transforms
+ * @return
+ */
+ private List getNodeTransformList(final List transforms) {
+ final List transformList = Lists.newArrayList();
+
+ for (final Element transform : transforms) {
+ final double[] array = _colladaDOMUtil.parseDoubleArray(transform);
+
+ if ("translate".equals(transform.getName())) {
+ transformList.add(new TransformElement(array, TransformElementType.Translation));
+ } else if ("rotate".equals(transform.getName())) {
+ transformList.add(new TransformElement(array, TransformElementType.Rotation));
+ } else if ("scale".equals(transform.getName())) {
+ transformList.add(new TransformElement(array, TransformElementType.Scale));
+ } else if ("matrix".equals(transform.getName())) {
+ transformList.add(new TransformElement(array, TransformElementType.Matrix));
+ } else if ("lookat".equals(transform.getName())) {
+ transformList.add(new TransformElement(array, TransformElementType.Lookat));
+ } else {
+ if (logger.isLoggable(Level.WARNING)) {
+ logger.warning("transform not currently supported: " + transform.getClass().getCanonicalName());
+ }
+ }
+ }
+
+ return transformList;
+ }
+
+ /**
+ * Bake a list of TransformElements into an Ardor3D Transform object.
+ *
+ * @param transforms
+ * @return
+ */
+ private Transform bakeTransforms(final List transforms) {
+ final Matrix4 workingMat = Matrix4.fetchTempInstance();
+ final Matrix4 finalMat = Matrix4.fetchTempInstance();
+ finalMat.setIdentity();
+ for (final TransformElement transform : transforms) {
+ final double[] array = transform.getArray();
+ final TransformElementType type = transform.getType();
+ if (type == TransformElementType.Translation) {
+ workingMat.setIdentity();
+ workingMat.setColumn(3, new Vector4(array[0], array[1], array[2], 1.0));
+ finalMat.multiplyLocal(workingMat);
+ } else if (type == TransformElementType.Rotation) {
+ 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 (type == TransformElementType.Scale) {
+ workingMat.setIdentity();
+ workingMat.scale(new Vector4(array[0], array[1], array[2], 1), workingMat);
+ finalMat.multiplyLocal(workingMat);
+ } else if (type == TransformElementType.Matrix) {
+ workingMat.fromArray(array);
+ finalMat.multiplyLocal(workingMat);
+ } else if (type == TransformElementType.Lookat) {
+ 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.0));
+ finalMat.multiplyLocal(workingMat);
+ } else {
+ if (logger.isLoggable(Level.WARNING)) {
+ logger.warning("transform not currently supported: " + transform.getClass().getCanonicalName());
+ }
+ }
+ }
+ return new Transform().fromHomogeneousMatrix(finalMat);
+ }
+
+ /**
+ * Util for making a readable string out of a xml element hierarchy
+ *
+ * @param e
+ * @param maxDepth
+ * @return
+ */
+ public static String getElementString(final Element e, final int maxDepth) {
+ return getElementString(e, maxDepth, true);
+ }
+
+ public static String getElementString(final Element e, final int maxDepth, final boolean showDots) {
+ final StringBuilder str = new StringBuilder();
+ getElementString(e, str, 0, maxDepth, showDots);
+ return str.toString();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void getElementString(final Element e, final StringBuilder str, final int depth, final int maxDepth,
+ final boolean showDots) {
+ addSpacing(str, depth);
+ str.append("<");
+ str.append(e.getName());
+ str.append(" ");
+ final List attrs = e.getAttributes();
+ for (int i = 0; i < attrs.size(); i++) {
+ final Attribute attr = attrs.get(i);
+ str.append(attr.getName());
+ str.append("=\"");
+ str.append(attr.getValue());
+ str.append("\"");
+ if (i < attrs.size() - 1) {
+ str.append(" ");
+ }
+ }
+ if (!e.getChildren().isEmpty() || !"".equals(e.getText())) {
+ str.append(">");
+ if (depth < maxDepth) {
+ str.append("\n");
+ for (final Element child : (List) e.getChildren()) {
+ getElementString(child, str, depth + 1, maxDepth, showDots);
+ }
+ if (!"".equals(e.getText())) {
+ addSpacing(str, depth + 1);
+ str.append(e.getText());
+ str.append("\n");
+ }
+ } else if (showDots) {
+ str.append("\n");
+ addSpacing(str, depth + 1);
+ str.append("...");
+ str.append("\n");
+ }
+ addSpacing(str, depth);
+ str.append("");
+ str.append(e.getName());
+ str.append(">");
+ } else {
+ str.append("/>");
+ }
+ str.append("\n");
+ }
+
+ private static void addSpacing(final StringBuilder str, final int depth) {
+ for (int i = 0; i < depth; i++) {
+ str.append(" ");
+ }
+ }
+
+ private enum AccessorType {
+ None, Vector, Matrix
+ }
+
+ private static class Target {
+ public String id;
+ public List sids = Lists.newArrayList();
+ public AccessorType accessorType = AccessorType.None;
+ public int accessorIndexX = -1, accessorIndexY = -1;
+
+ @Override
+ public String toString() {
+ if (accessorType == AccessorType.None) {
+ return "Target [accessorType=" + accessorType + ", id=" + id + ", sids=" + sids + "]";
+ }
+ return "Target [accessorType=" + accessorType + ", accessorIndexX=" + accessorIndexX + ", accessorIndexY="
+ + accessorIndexY + ", id=" + id + ", sids=" + sids + "]";
+ }
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaDOMUtil.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaDOMUtil.java
new file mode 100644
index 0000000..28728fd
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaDOMUtil.java
@@ -0,0 +1,378 @@
+/**
+ * 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 .
+ */
+
+package com.ardor3d.extension.model.collada.jdom;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jdom.Attribute;
+import org.jdom.CDATA;
+import org.jdom.Comment;
+import org.jdom.DataConversionException;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.ProcessingInstruction;
+import org.jdom.Text;
+import org.jdom.xpath.XPath;
+
+import com.ardor3d.extension.model.collada.jdom.data.DataCache;
+import com.ardor3d.math.ColorRGBA;
+
+/**
+ * Utility methods for parsing Collada data related to node hierarchy and arrays, using XPath or arrays extracted during
+ * sax parsing.
+ */
+public class ColladaDOMUtil {
+ private final Logger logger = Logger.getLogger(ColladaDOMUtil.class.getName());
+
+ private final DataCache _dataCache;
+
+ public ColladaDOMUtil(final DataCache dataCache) {
+ _dataCache = dataCache;
+ }
+
+ /**
+ * Find element with specific id
+ *
+ * @param baseUrl
+ * url specifying target id
+ * @return element with specific id or null if not found
+ */
+ public Element findTargetWithId(final String baseUrl) {
+ return _dataCache.getIdCache().get(parseUrl(baseUrl));
+ }
+
+ /**
+ * Find element with specific sid
+ *
+ * @param baseUrl
+ * url specifying target sid
+ * @return element with specific id or null if not found
+ */
+ public Element findTargetWithSid(final String baseUrl) {
+ return _dataCache.getSidCache().get(parseUrl(baseUrl));
+ }
+
+ /**
+ * Select nodes through an XPath query and return all hits as a List
+ *
+ * @param element
+ * root element to start search on
+ * @param query
+ * XPath expression
+ * @return the list of selected items, which may be of types: {@link Element}, {@link Attribute}, {@link Text},
+ * {@link CDATA}, {@link Comment}, {@link ProcessingInstruction}, Boolean, Double, or String.
+ */
+ public List> selectNodes(final Element element, final String query) {
+ final XPath xPathExpression = getXPathExpression(query);
+
+ try {
+ return xPathExpression.selectNodes(element);
+ } catch (final JDOMException e) {
+ e.printStackTrace();
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Select nodes through an XPath query and returns the first hit
+ *
+ * @param element
+ * root element to start search on
+ * @param query
+ * XPath expression
+ * @return the first selected item, which may be of types: {@link Element}, {@link Attribute}, {@link Text},
+ * {@link CDATA}, {@link Comment}, {@link ProcessingInstruction}, Boolean, Double, String, or
+ * null
if no item was selected.
+ */
+ public Object selectSingleNode(final Element element, final String query) {
+ final XPath xPathExpression = getXPathExpression(query);
+
+ try {
+ return xPathExpression.selectSingleNode(element);
+ } catch (final JDOMException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private String parseUrl(String baseUrl) {
+ if (baseUrl.startsWith("#")) {
+ baseUrl = baseUrl.substring(1);
+ }
+ return baseUrl;
+ }
+
+ /**
+ * Compiles and return an XPath expression. Expressions are cached.
+ *
+ * @param query
+ * XPath query to compile
+ * @return new XPath expression object
+ */
+ private XPath getXPathExpression(final String query) {
+
+ if (_dataCache.getxPathExpressions().containsKey(query)) {
+ return _dataCache.getxPathExpressions().get(query);
+ }
+
+ XPath xPathExpression = null;
+ try {
+ xPathExpression = XPath.newInstance(query);
+ } catch (final JDOMException e) {
+ e.printStackTrace();
+ }
+
+ _dataCache.getxPathExpressions().put(query, xPathExpression);
+
+ return xPathExpression;
+ }
+
+ /**
+ * Parses the text under a node and returns it as a float array.
+ *
+ * @param node
+ * node to parse content from
+ * @return parsed float array
+ */
+ public float[] parseFloatArray(final Element node) {
+ if (_dataCache.getFloatArrays().containsKey(node)) {
+ return _dataCache.getFloatArrays().get(node);
+ }
+
+ final String content = node.getText();
+
+ final List list = new ArrayList();
+ final StringTokenizer tokenizer = new StringTokenizer(content, " ");
+ while (tokenizer.hasMoreTokens()) {
+ list.add(tokenizer.nextToken());
+ }
+ final int listSize = list.size();
+ final float[] floatArray = new float[listSize];
+ for (int i = 0; i < listSize; i++) {
+ floatArray[i] = Float.parseFloat(list.get(i).replace(",", "."));
+ }
+
+ _dataCache.getFloatArrays().put(node, floatArray);
+
+ return floatArray;
+ }
+
+ /**
+ * Parses the text under a node and returns it as a double array.
+ *
+ * @param node
+ * node to parse content from
+ * @return parsed double array
+ */
+ public double[] parseDoubleArray(final Element node) {
+ if (_dataCache.getDoubleArrays().containsKey(node)) {
+ return _dataCache.getDoubleArrays().get(node);
+ }
+
+ final String content = node.getText();
+
+ final List list = new ArrayList();
+ final StringTokenizer tokenizer = new StringTokenizer(content, " ");
+ while (tokenizer.hasMoreTokens()) {
+ list.add(tokenizer.nextToken());
+ }
+ final int listSize = list.size();
+ final double[] doubleArray = new double[listSize];
+ for (int i = 0; i < listSize; i++) {
+ doubleArray[i] = Double.parseDouble(list.get(i).replace(",", "."));
+ }
+
+ _dataCache.getDoubleArrays().put(node, doubleArray);
+
+ return doubleArray;
+ }
+
+ /**
+ * Parses the text under a node and returns it as an int array.
+ *
+ * @param node
+ * node to parse content from
+ * @return parsed int array
+ */
+ public int[] parseIntArray(final Element node) {
+ if (_dataCache.getIntArrays().containsKey(node)) {
+ return _dataCache.getIntArrays().get(node);
+ }
+
+ final String content = node.getText();
+
+ final List list = new ArrayList();
+ final StringTokenizer tokenizer = new StringTokenizer(content, " ");
+ while (tokenizer.hasMoreTokens()) {
+ list.add(tokenizer.nextToken());
+ }
+ final int listSize = list.size();
+ final int[] intArray = new int[listSize];
+ for (int i = 0; i < listSize; i++) {
+ intArray[i] = Integer.parseInt(list.get(i));
+ }
+
+ _dataCache.getIntArrays().put(node, intArray);
+
+ return intArray;
+ }
+
+ /**
+ * Parses the text under a node and returns it as a boolean array.
+ *
+ * @param node
+ * node to parse content from
+ * @return parsed boolean array
+ */
+ public boolean[] parseBooleanArray(final Element node) {
+ if (_dataCache.getDoubleArrays().containsKey(node)) {
+ return _dataCache.getBooleanArrays().get(node);
+ }
+
+ final String content = node.getText();
+
+ final List list = new ArrayList();
+ final StringTokenizer tokenizer = new StringTokenizer(content, " ");
+ while (tokenizer.hasMoreTokens()) {
+ list.add(tokenizer.nextToken());
+ }
+ final int listSize = list.size();
+ final boolean[] booleanArray = new boolean[listSize];
+ for (int i = 0; i < listSize; i++) {
+ booleanArray[i] = Boolean.parseBoolean(list.get(i));
+ }
+
+ _dataCache.getBooleanArrays().put(node, booleanArray);
+
+ return booleanArray;
+ }
+
+ /**
+ * Parses the text under a node and returns it as a string array.
+ *
+ * @param node
+ * node to parse content from
+ * @return parsed string array
+ */
+ public String[] parseStringArray(final Element node) {
+ if (_dataCache.getStringArrays().containsKey(node)) {
+ return _dataCache.getStringArrays().get(node);
+ }
+
+ final String content = node.getText();
+
+ final List list = new ArrayList();
+ final StringTokenizer tokenizer = new StringTokenizer(content, " ");
+ while (tokenizer.hasMoreTokens()) {
+ list.add(tokenizer.nextToken());
+ }
+ final String[] stringArray = list.toArray(new String[list.size()]);
+
+ _dataCache.getStringArrays().put(node, stringArray);
+
+ return stringArray;
+ }
+
+ /**
+ * Strips the namespace from all nodes in a tree.
+ *
+ * @param rootElement
+ * Root of strip operation
+ */
+ public void stripNamespace(final Element rootElement) {
+ rootElement.setNamespace(null);
+
+ @SuppressWarnings("unchecked")
+ final List children = rootElement.getChildren();
+ final Iterator i = children.iterator();
+ while (i.hasNext()) {
+ final Element child = i.next();
+ stripNamespace(child);
+ }
+ }
+
+ /**
+ * Parse an int value in an attribute.
+ *
+ * @param input
+ * Element containing the attribute
+ * @param attributeName
+ * Attribute name to parse a value for
+ * @return parsed integer
+ */
+ public int getAttributeIntValue(final Element input, final String attributeName, final int defaultVal) {
+ final Attribute attribute = input.getAttribute(attributeName);
+ if (attribute != null) {
+ try {
+ return attribute.getIntValue();
+ } catch (final DataConversionException e) {
+ logger.log(Level.WARNING, "Could not parse int value", e);
+ }
+ }
+ return defaultVal;
+ }
+
+ /**
+ * Convert a Collada color description into an Ardor3D ColorRGBA
+ *
+ * @param colorDescription
+ * Collada color description
+ * @return Ardor3d ColorRGBA
+ */
+ public ColorRGBA getColor(final String colorDescription) {
+ if (colorDescription == null) {
+ throw new ColladaException("Null color description not allowed", null);
+ }
+
+ final String[] values = _dataCache.getPattern().split(colorDescription.replace(",", "."));
+
+ if (values.length < 3 || values.length > 4) {
+ throw new ColladaException("Expected color definition of length 3 or 4 - got " + values.length
+ + " for description: " + colorDescription, colorDescription);
+ }
+
+ try {
+ return new ColorRGBA(Float.parseFloat(values[0]), Float.parseFloat(values[1]), Float.parseFloat(values[2]),
+ values.length == 4 ? Float.parseFloat(values[3]) : 1.0f);
+ } catch (final NumberFormatException e) {
+ throw new ColladaException("Unable to parse float number", colorDescription, e);
+ }
+ }
+
+ /**
+ * Find Element with semantic POSITION under an element with inputs
+ *
+ * @param v
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ public Element getPositionSource(final Element v) {
+ for (final Element input : (List) v.getChildren("input")) {
+ if ("POSITION".equals(input.getAttributeValue("semantic"))) {
+ final Element n = findTargetWithId(input.getAttributeValue("source"));
+ if (n != null && "source".equals(n.getName())) {
+ return n;
+ }
+ }
+ }
+
+ // changed this to throw an exception instead - otherwise, there will just be a nullpointer exception
+ // outside. This provides much more information about what went wrong / Petter
+ // return null;
+ throw new ColladaException("Unable to find POSITION semantic for inputs under DaeVertices", v);
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaException.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaException.java
new file mode 100644
index 0000000..b4d4927
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaException.java
@@ -0,0 +1,31 @@
+/**
+ * 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 .
+ */
+
+package com.ardor3d.extension.model.collada.jdom;
+
+/**
+ * Customer exception thrown when something unexpected is encountered in a Collada file.
+ */
+public class ColladaException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public ColladaException(final String message, final Object source) {
+ super(ColladaException.createMessage(message, source));
+ }
+
+ public ColladaException(final String msg, final Object source, final Throwable cause) {
+ super(ColladaException.createMessage(msg, source), cause);
+ }
+
+ private static String createMessage(final String message, final Object source) {
+ return "Collada problem for source: " + (source != null ? source.toString() : "null") + ": " + message;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaImporter.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaImporter.java
new file mode 100644
index 0000000..89208cf
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaImporter.java
@@ -0,0 +1,498 @@
+/**
+ * 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 .
+ */
+
+package com.ardor3d.extension.model.collada.jdom;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.logging.Logger;
+
+import org.jdom.Attribute;
+import org.jdom.DataConversionException;
+import org.jdom.DefaultJDOMFactory;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.Namespace;
+import org.jdom.Text;
+import org.jdom.input.SAXBuilder;
+import org.jdom.input.SAXHandler;
+import org.xml.sax.SAXException;
+
+import com.ardor3d.extension.animation.skeletal.Joint;
+import com.ardor3d.extension.model.collada.jdom.data.AssetData;
+import com.ardor3d.extension.model.collada.jdom.data.ColladaStorage;
+import com.ardor3d.extension.model.collada.jdom.data.DataCache;
+import com.ardor3d.extension.model.collada.jdom.plugin.ColladaExtraPlugin;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.util.geom.GeometryTool.MatchCondition;
+import com.ardor3d.util.resource.RelativeResourceLocator;
+import com.ardor3d.util.resource.ResourceLocator;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.ResourceSource;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+/**
+ * Main class for importing Collada files.
+ *
+ * Example usages:
+ *
new ColladaImporter().load(resource);
+ * new ColladaImporter().loadTextures(false).modelLocator(locator).load(resource);
+ *
+ */
+public class ColladaImporter {
+ private boolean _loadTextures = true;
+ private boolean _flipTransparency = false;
+ private boolean _loadAnimations = true;
+ private ResourceLocator _textureLocator;
+ private ResourceLocator _modelLocator;
+ private boolean _compressTextures = false;
+ private boolean _optimizeMeshes = false;
+ private final EnumSet _optimizeSettings = EnumSet.of(MatchCondition.UVs, MatchCondition.Normal,
+ MatchCondition.Color);
+ private Map _externalJointMapping;
+ private final List _extraPlugins = Lists.newArrayList();
+
+ public boolean isLoadTextures() {
+ return _loadTextures;
+ }
+
+ public ColladaImporter setLoadTextures(final boolean loadTextures) {
+ _loadTextures = loadTextures;
+ return this;
+ }
+
+ public boolean isCompressTextures() {
+ return _compressTextures;
+ }
+
+ public ColladaImporter setCompressTextures(final boolean compressTextures) {
+ _compressTextures = compressTextures;
+ return this;
+ }
+
+ public boolean isLoadAnimations() {
+ return _loadAnimations;
+ }
+
+ public ColladaImporter setLoadAnimations(final boolean loadAnimations) {
+ _loadAnimations = loadAnimations;
+ return this;
+ }
+
+ public boolean isFlipTransparency() {
+ return _flipTransparency;
+ }
+
+ public void addExtraPlugin(final ColladaExtraPlugin plugin) {
+ _extraPlugins.add(plugin);
+ }
+
+ public void clearExtraPlugins() {
+ _extraPlugins.clear();
+ }
+
+ /**
+ * @param flipTransparency
+ * if true, invert the value of any "transparency" entries found - required for some exporters.
+ * @return this importer, for chaining
+ */
+ public ColladaImporter setFlipTransparency(final boolean flipTransparency) {
+ _flipTransparency = flipTransparency;
+ return this;
+ }
+
+ public ResourceLocator getTextureLocator() {
+ return _textureLocator;
+ }
+
+ public ColladaImporter setTextureLocator(final ResourceLocator textureLocator) {
+ _textureLocator = textureLocator;
+ return this;
+ }
+
+ public ColladaImporter setExternalJoints(final Map map) {
+ _externalJointMapping = map;
+ return this;
+ }
+
+ public Map getExternalJoints() {
+ return _externalJointMapping;
+ }
+
+ public ResourceLocator getModelLocator() {
+ return _modelLocator;
+ }
+
+ public ColladaImporter setModelLocator(final ResourceLocator modelLocator) {
+ _modelLocator = modelLocator;
+ return this;
+ }
+
+ public boolean isOptimizeMeshes() {
+ return _optimizeMeshes;
+ }
+
+ public void setOptimizeMeshes(final boolean optimizeMeshes) {
+ _optimizeMeshes = optimizeMeshes;
+ }
+
+ public Set getOptimizeSettings() {
+ return ImmutableSet.copyOf(_optimizeSettings);
+ }
+
+ public void setOptimizeSettings(final MatchCondition... optimizeSettings) {
+ _optimizeSettings.clear();
+ for (final MatchCondition cond : optimizeSettings) {
+ _optimizeSettings.add(cond);
+ }
+ }
+
+ /**
+ * Reads a Collada file from the given resource and returns it as a ColladaStorage object.
+ *
+ * @param resource
+ * the name of the resource to find. ResourceLocatorTool will be used with TYPE_MODEL to find the
+ * resource.
+ * @return a ColladaStorage data object containing the Collada scene and other useful elements.
+ * @throws IOException
+ * if the resource can not be located or loaded for some reason.
+ */
+ public ColladaStorage load(final String resource) throws IOException {
+ final ResourceSource source;
+ if (_modelLocator == null) {
+ source = ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_MODEL, resource);
+ } else {
+ source = _modelLocator.locateResource(resource);
+ }
+
+ if (source == null) {
+ throw new IOException("Unable to locate '" + resource + "'");
+ }
+
+ return load(source);
+ }
+
+ /**
+ * Reads a Collada file from the given resource and returns it as a ColladaStorage object.
+ *
+ * @param resource
+ * the name of the resource to find.
+ * @return a ColladaStorage data object containing the Collada scene and other useful elements.
+ * @throws IOException
+ * if the resource can not be loaded for some reason.
+ */
+ public ColladaStorage load(final ResourceSource resource) throws IOException {
+ final ColladaStorage colladaStorage = new ColladaStorage();
+ final DataCache dataCache = new DataCache();
+ if (_externalJointMapping != null) {
+ dataCache.getExternalJointMapping().putAll(_externalJointMapping);
+ }
+ final ColladaDOMUtil colladaDOMUtil = new ColladaDOMUtil(dataCache);
+ final ColladaMaterialUtils colladaMaterialUtils = new ColladaMaterialUtils(this, dataCache, colladaDOMUtil);
+ final ColladaMeshUtils colladaMeshUtils = new ColladaMeshUtils(dataCache, colladaDOMUtil, colladaMaterialUtils,
+ _optimizeMeshes, _optimizeSettings);
+ final ColladaAnimUtils colladaAnimUtils = new ColladaAnimUtils(colladaStorage, dataCache, colladaDOMUtil,
+ colladaMeshUtils);
+ final ColladaNodeUtils colladaNodeUtils = new ColladaNodeUtils(dataCache, colladaDOMUtil, colladaMaterialUtils,
+ colladaMeshUtils, colladaAnimUtils);
+
+ try {
+ // Pull in the DOM tree of the Collada resource.
+ final Element collada = readCollada(resource, dataCache);
+
+ // if we don't specify a texture locator, add a temporary texture locator at the location of this model
+ // resource..
+ final boolean addLocator = _textureLocator == null;
+
+ final RelativeResourceLocator loc;
+ if (addLocator) {
+ loc = new RelativeResourceLocator(resource);
+ ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, loc);
+ } else {
+ loc = null;
+ }
+
+ final AssetData assetData = colladaNodeUtils.parseAsset(collada.getChild("asset"));
+
+ // Collada may or may not have a scene, so this can return null.
+ final Node scene = colladaNodeUtils.getVisualScene(collada);
+
+ if (_loadAnimations) {
+ colladaAnimUtils.parseLibraryAnimations(collada);
+ }
+
+ // set our scene into storage
+ colladaStorage.setScene(scene);
+
+ // set our asset data into storage
+ colladaStorage.setAssetData(assetData);
+
+ // drop our added locator if needed.
+ if (addLocator) {
+ ResourceLocatorTool.removeResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, loc);
+ }
+
+ // copy across our mesh colors - only for objects with multiple channels
+ final Multimap colors = ArrayListMultimap.create();
+ final Multimap temp = dataCache.getParsedVertexColors();
+ for (final MeshData key : temp.keySet()) {
+ // only copy multiple channels since their data is lost
+ final Collection val = temp.get(key);
+ if (val != null && val.size() > 1) {
+ colors.putAll(key, val);
+ }
+ }
+ colladaStorage.setParsedVertexColors(colors);
+
+ // copy across our mesh material info
+ colladaStorage.setMeshMaterialInfo(dataCache.getMeshMaterialMap());
+ colladaStorage.setMaterialMap(dataCache.getMaterialInfoMap());
+
+ // return storage
+ return colladaStorage;
+ } catch (final Exception e) {
+ throw new IOException("Unable to load collada resource from URL: " + resource, e);
+ }
+ }
+
+ /**
+ * Reads the whole Collada DOM tree from the given resource and returns its root element. Exceptions may be thrown
+ * by underlying tools; these will be wrapped in a RuntimeException and rethrown.
+ *
+ * @param resource
+ * the ResourceSource to read the resource from
+ * @return the Collada root element
+ */
+ private Element readCollada(final ResourceSource resource, final DataCache dataCache) {
+ try {
+ final SAXBuilder builder = new SAXBuilder() {
+ @Override
+ protected SAXHandler createContentHandler() {
+ return new SAXHandler(new ArdorFactory(dataCache)) {
+ @Override
+ public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+ // Just kill what's usually done here...
+ }
+
+ };
+ }
+ };
+
+ final Document doc = builder.build(resource.openStream());
+ final Element collada = doc.getRootElement();
+
+ // ColladaDOMUtil.stripNamespace(collada);
+
+ return collada;
+ } catch (final Exception e) {
+ throw new RuntimeException("Unable to load collada resource from source: " + resource, e);
+ }
+ }
+
+ private enum BufferType {
+ None, Float, Double, Int, String, P
+ }
+
+ /**
+ * A JDOMFactory that normalizes all text (strips extra whitespace etc), preparses all arrays and hashes all
+ * elements based on their id/sid.
+ */
+ private static final class ArdorFactory extends DefaultJDOMFactory {
+ private final Logger logger = Logger.getLogger(ArdorFactory.class.getName());
+
+ private final DataCache dataCache;
+ private Element currentElement;
+ private BufferType bufferType = BufferType.None;
+ private int count = 0;
+ private final List list = new ArrayList();
+
+ ArdorFactory(final DataCache dataCache) {
+ this.dataCache = dataCache;
+ }
+
+ @Override
+ public Text text(final String text) {
+ try {
+ switch (bufferType) {
+ case Float: {
+ final String normalizedText = Text.normalizeString(text);
+ if (normalizedText.length() == 0) {
+ return new Text("");
+ }
+ final StringTokenizer tokenizer = new StringTokenizer(normalizedText, " ");
+ final float[] floatArray = new float[count];
+ for (int i = 0; i < count; i++) {
+ floatArray[i] = parseFloat(tokenizer.nextToken());
+ }
+
+ dataCache.getFloatArrays().put(currentElement, floatArray);
+
+ return new Text("");
+ }
+ case Double: {
+ final String normalizedText = Text.normalizeString(text);
+ if (normalizedText.length() == 0) {
+ return new Text("");
+ }
+ final StringTokenizer tokenizer = new StringTokenizer(normalizedText, " ");
+ final double[] doubleArray = new double[count];
+ for (int i = 0; i < count; i++) {
+ doubleArray[i] = Double.parseDouble(tokenizer.nextToken().replace(",", "."));
+ }
+
+ dataCache.getDoubleArrays().put(currentElement, doubleArray);
+
+ return new Text("");
+ }
+ case Int: {
+ final String normalizedText = Text.normalizeString(text);
+ if (normalizedText.length() == 0) {
+ return new Text("");
+ }
+ final StringTokenizer tokenizer = new StringTokenizer(normalizedText, " ");
+ final int[] intArray = new int[count];
+ int i = 0;
+ while (tokenizer.hasMoreTokens()) {
+ intArray[i++] = Integer.parseInt(tokenizer.nextToken());
+ }
+
+ dataCache.getIntArrays().put(currentElement, intArray);
+
+ return new Text("");
+ }
+ case P: {
+ list.clear();
+ final String normalizedText = Text.normalizeString(text);
+ if (normalizedText.length() == 0) {
+ return new Text("");
+ }
+ final StringTokenizer tokenizer = new StringTokenizer(normalizedText, " ");
+ while (tokenizer.hasMoreTokens()) {
+ list.add(tokenizer.nextToken());
+ }
+ final int listSize = list.size();
+ final int[] intArray = new int[listSize];
+ for (int i = 0; i < listSize; i++) {
+ intArray[i] = Integer.parseInt(list.get(i));
+ }
+
+ dataCache.getIntArrays().put(currentElement, intArray);
+
+ return new Text("");
+ }
+ }
+ } catch (final NoSuchElementException e) {
+ throw new ColladaException("Number of values in collada array does not match its count attribute: "
+ + count, e);
+ }
+ return new Text(Text.normalizeString(text));
+ }
+
+ @Override
+ public void setAttribute(final Element parent, final Attribute a) {
+ if ("id".equals(a.getName())) {
+ if (dataCache.getIdCache().containsKey(a.getValue())) {
+ logger.warning("id already exists in id cache: " + a.getValue());
+ }
+ dataCache.getIdCache().put(a.getValue(), parent);
+ } else if ("sid".equals(a.getName())) {
+ dataCache.getSidCache().put(a.getValue(), parent);
+ } else if ("count".equals(a.getName())) {
+ try {
+ count = a.getIntValue();
+ } catch (final DataConversionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ super.setAttribute(parent, a);
+ }
+
+ @Override
+ public Element element(final String name, final Namespace namespace) {
+ currentElement = super.element(name);
+ handleTypes(name);
+ return currentElement;
+ }
+
+ @Override
+ public Element element(final String name, final String prefix, final String uri) {
+ currentElement = super.element(name);
+ handleTypes(name);
+ return currentElement;
+ }
+
+ @Override
+ public Element element(final String name, final String uri) {
+ currentElement = super.element(name);
+ handleTypes(name);
+ return currentElement;
+ }
+
+ @Override
+ public Element element(final String name) {
+ currentElement = super.element(name);
+ handleTypes(name);
+ return currentElement;
+ }
+
+ private void handleTypes(final String name) {
+ if ("float_array".equals(name)) {
+ bufferType = BufferType.Float;
+ } else if ("double_array".equals(name)) {
+ bufferType = BufferType.Double;
+ } else if ("int_array".equals(name)) {
+ bufferType = BufferType.Int;
+ } else if ("p".equals(name)) {
+ bufferType = BufferType.P;
+ } else {
+ bufferType = BufferType.None;
+ }
+ }
+ }
+
+ /**
+ * Parse a numeric value. Commas are replaced by dot automaticly. Also handle special values : INF, -INF, NaN
+ *
+ * @param number
+ * string
+ * @return float
+ */
+ public static float parseFloat(String candidate) {
+ candidate = candidate.replace(',', '.');
+ if (candidate.contains("-INF")) {
+ return Float.NEGATIVE_INFINITY;
+ } else if (candidate.contains("INF")) {
+ return Float.POSITIVE_INFINITY;
+ }
+ return Float.parseFloat(candidate);
+ }
+
+ public boolean readExtra(final Element extra, final Object... params) {
+ for (final ColladaExtraPlugin plugin : _extraPlugins) {
+ if (plugin.processExtra(extra, params)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaInputPipe.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaInputPipe.java
new file mode 100644
index 0000000..9621973
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaInputPipe.java
@@ -0,0 +1,250 @@
+/**
+ * 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 .
+ */
+
+package com.ardor3d.extension.model.collada.jdom;
+
+import java.nio.FloatBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jdom.Element;
+
+import com.ardor3d.extension.model.collada.jdom.data.DataCache;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * The purpose of this class is to tie a