aboutsummaryrefslogtreecommitdiffstats
path: root/ardor3d-collada/src/main/java/com
diff options
context:
space:
mode:
authorneothemachine <[email protected]>2012-12-05 19:19:31 +0100
committerneothemachine <[email protected]>2012-12-05 19:19:31 +0100
commit7ca002eb1d1510767cd524a800710cf3898ba699 (patch)
tree7b3bd1dec9f58e32d083b287f44569526761bb32 /ardor3d-collada/src/main/java/com
parent105f890b5e750aaa5ec63e768e77eca2af8631a4 (diff)
parent9dd02f103042cb8a196f8a3ed2278da443e345bf (diff)
Merge branch 'kill_trunk' into dependencies
Diffstat (limited to 'ardor3d-collada/src/main/java/com')
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaAnimUtils.java1174
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaDOMUtil.java378
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaException.java31
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaImporter.java500
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaInputPipe.java250
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMaterialUtils.java586
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMeshUtils.java616
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaNodeUtils.java378
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AnimationItem.java94
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AssetData.java201
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ColladaStorage.java161
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ControllerStore.java16
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/DataCache.java229
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/JointNode.java54
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MaterialInfo.java98
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MeshVertPairs.java43
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/NodeType.java18
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SamplerTypes.java80
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SkinData.java126
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/TransformElement.java41
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/ColladaExtraPlugin.java19
-rw-r--r--ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/GoogleEarthPlugin.java44
22 files changed, 5137 insertions, 0 deletions
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..379651e
--- /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 <http://www.ardor3d.com/LICENSE>.
+ */
+
+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.jdom2.Attribute;
+import org.jdom2.DataConversionException;
+import org.jdom2.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<StateType, RenderState> 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 <instance_controller> element. We'll parse the skeleton reference from here.
+ * @param controller
+ * the referenced <controller> element. Used for naming purposes.
+ * @param skin
+ * our <skin> 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 <instance_controller> element
+ final List<Element> skeletonRoots = Lists.newArrayList();
+ for (final Element sk : 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<String> jointNames = Lists.newArrayList();
+ final List<Transform> bindMatrices = Lists.newArrayList();
+ final List<ColladaInputPipe.ParamType> paramTypes = Lists.newArrayList();
+
+ for (final Element inputEL : 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<Short> jointIndices = Lists.newArrayList();
+ final List<Float> jointWeights = Lists.newArrayList();
+ int indOff = 0, weightOff = 0;
+
+ int maxOffset = 0;
+ for (final Element inputEL : 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<MeshVertPairs> 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 <morph> 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 <controller> element. Used for naming purposes.
+ * @param morph
+ * our <morph> 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<Element, TargetChannel> 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<TargetChannel> targetList) {
+
+ final List<Element> elementTransforms = new ArrayList<Element>();
+ for (final Element child : parentElement.getChildren()) {
+ if (_dataCache.getTransformTypes().contains(child.getName())) {
+ elementTransforms.add(child);
+ }
+ }
+ final List<TransformElement> 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<Type, ColladaInputPipe> pipes = Maps.newEnumMap(Type.class);
+
+ final Element samplerElement = _colladaDOMUtil.findTargetWithId(source);
+ for (final Element inputElement : 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<Float> finalTimeList = Lists.newArrayList();
+ final List<Transform> finalTransformList = Lists.newArrayList();
+ final List<TargetChannel> 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<Element, TargetChannel> 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 : 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<Element> 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<Element> 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<String, Integer> 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<TransformElement> getNodeTransformList(final List<Element> transforms) {
+ final List<TransformElement> 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<TransformElement> 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<Attribute> 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 : 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<String> 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..2f67140
--- /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 <http://www.ardor3d.com/LICENSE>.
+ */
+
+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.jdom2.Attribute;
+import org.jdom2.CDATA;
+import org.jdom2.Comment;
+import org.jdom2.DataConversionException;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.ProcessingInstruction;
+import org.jdom2.Text;
+import org.jdom2.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
+ * <code>null</code> 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<String> list = new ArrayList<String>();
+ 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<String> list = new ArrayList<String>();
+ 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<String> list = new ArrayList<String>();
+ 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<String> list = new ArrayList<String>();
+ 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<String> list = new ArrayList<String>();
+ 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<Element> children = rootElement.getChildren();
+ final Iterator<Element> 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 : 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 <http://www.ardor3d.com/LICENSE>.
+ */
+
+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..89906c1
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaImporter.java
@@ -0,0 +1,500 @@
+/**
+ * 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.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.jdom2.Attribute;
+import org.jdom2.DataConversionException;
+import org.jdom2.DefaultJDOMFactory;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMFactory;
+import org.jdom2.Namespace;
+import org.jdom2.Text;
+import org.jdom2.input.SAXBuilder;
+import org.jdom2.input.sax.SAXHandler;
+import org.jdom2.input.sax.SAXHandlerFactory;
+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.
+ * <p>
+ * Example usages:
+ * <li>new ColladaImporter().load(resource);</li>
+ * <li>new ColladaImporter().loadTextures(false).modelLocator(locator).load(resource);</li>
+ * </p>
+ */
+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<MatchCondition> _optimizeSettings = EnumSet.of(MatchCondition.UVs, MatchCondition.Normal,
+ MatchCondition.Color);
+ private Map<String, Joint> _externalJointMapping;
+ private final List<ColladaExtraPlugin> _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<String, Joint> map) {
+ _externalJointMapping = map;
+ return this;
+ }
+
+ public Map<String, Joint> 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<MatchCondition> 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<MeshData, FloatBuffer> colors = ArrayListMultimap.create();
+ final Multimap<MeshData, FloatBuffer> temp = dataCache.getParsedVertexColors();
+ for (final MeshData key : temp.keySet()) {
+ // only copy multiple channels since their data is lost
+ final Collection<FloatBuffer> 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 JDOMFactory jdomFac = new ArdorFactory(dataCache);
+ final SAXBuilder builder = new SAXBuilder(null, new SAXHandlerFactory() {
+ @Override
+ public SAXHandler createSAXHandler(final JDOMFactory factory) {
+ return new SAXHandler(jdomFac) {
+ @Override
+ public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
+ // Just kill what's usually done here...
+ }
+ };
+ }
+ }, jdomFac);
+
+ 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<String> list = new ArrayList<String>();
+
+ ArdorFactory(final DataCache dataCache) {
+ this.dataCache = dataCache;
+ }
+
+ @Override
+ public Text text(final int line, final int col, 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 int line, final int col, final String name, final Namespace namespace) {
+ currentElement = super.element(line, col, name);
+ handleTypes(name);
+ return currentElement;
+ }
+
+ @Override
+ public Element element(final int line, final int col, final String name, final String prefix, final String uri) {
+ currentElement = super.element(line, col, name);
+ handleTypes(name);
+ return currentElement;
+ }
+
+ @Override
+ public Element element(final int line, final int col, final String name, final String uri) {
+ currentElement = super.element(line, col, name);
+ handleTypes(name);
+ return currentElement;
+ }
+
+ @Override
+ public Element element(final int line, final int col, final String name) {
+ currentElement = super.element(line, col, 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..3f3a87e
--- /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 <http://www.ardor3d.com/LICENSE>.
+ */
+
+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.jdom2.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 <source> and accessor together to pull out data.
+ */
+public class ColladaInputPipe {
+ private static final Logger logger = Logger.getLogger(ColladaInputPipe.class.getName());
+
+ private final int _offset;
+ private final int _set;
+ private final Element _source;
+ private int _paramCount;
+ private SourceData _sourceData = null;
+ private Type _type;
+ private FloatBuffer _buffer;
+ private int _texCoord = 0;
+
+ public enum Type {
+ VERTEX, POSITION, NORMAL, TEXCOORD, COLOR, JOINT, WEIGHT, TEXTANGENT, TEXBINORMAL, //
+ INV_BIND_MATRIX, INPUT, IN_TANGENT, OUT_TANGENT, OUTPUT, INTERPOLATION, UNKNOWN
+ }
+
+ static class SourceData {
+ int count;
+ int stride;
+ int offset;
+
+ ParamType paramType;
+ float[] floatArray;
+ boolean[] boolArray;
+ int[] intArray;
+ String[] stringArray;
+
+ @Override
+ public String toString() {
+ switch (paramType) {
+ case bool_param:
+ return "SourceData [boolArray=" + Arrays.toString(boolArray) + "]";
+ case float_param:
+ return "SourceData [floatArray=" + Arrays.toString(floatArray) + "]";
+ case idref_param:
+ return "SourceData [idrefArray=" + Arrays.toString(stringArray) + "]";
+ case int_param:
+ return "SourceData [intArray=" + Arrays.toString(intArray) + "]";
+ case name_param:
+ return "SourceData [nameArray=" + Arrays.toString(stringArray) + "]";
+ default:
+ return "Unknown paramType";
+ }
+ }
+ }
+
+ public enum ParamType {
+ float_param, bool_param, int_param, name_param, idref_param
+ }
+
+ @SuppressWarnings("unchecked")
+ public ColladaInputPipe(final ColladaDOMUtil colladaDOMUtil, final Element input) {
+ // Setup our type
+ try {
+ _type = Type.valueOf(input.getAttributeValue("semantic"));
+ } catch (final Exception ex) {
+ ColladaInputPipe.logger.warning("Unknown input type: " + input.getAttributeValue("semantic"));
+ _type = Type.UNKNOWN;
+ }
+
+ // Locate our source
+ final Element n = colladaDOMUtil.findTargetWithId(input.getAttributeValue("source"));
+ if (n == null) {
+ throw new ColladaException("Input source not found: " + input.getAttributeValue("source"), input);
+ }
+
+ if ("source".equals(n.getName())) {
+ _source = n;
+ } else if ("vertices".equals(n.getName())) {
+ _source = colladaDOMUtil.getPositionSource(n);
+ } else {
+ throw new ColladaException("Input source not found: " + input.getAttributeValue("source"), input);
+ }
+
+ // TODO: Need to go through the params and see if they have a name set, and skip values if not when
+ // parsing the array?
+
+ _sourceData = new SourceData();
+ if (_source.getChild("float_array") != null) {
+ _sourceData.floatArray = colladaDOMUtil.parseFloatArray(_source.getChild("float_array"));
+ _sourceData.paramType = ParamType.float_param;
+ } else if (_source.getChild("bool_array") != null) {
+ _sourceData.boolArray = colladaDOMUtil.parseBooleanArray(_source.getChild("bool_array"));
+ _sourceData.paramType = ParamType.bool_param;
+ } else if (_source.getChild("int_array") != null) {
+ _sourceData.intArray = colladaDOMUtil.parseIntArray(_source.getChild("int_array"));
+ _sourceData.paramType = ParamType.int_param;
+ } else if (_source.getChild("Name_array") != null) {
+ _sourceData.stringArray = colladaDOMUtil.parseStringArray(_source.getChild("Name_array"));
+ _sourceData.paramType = ParamType.name_param;
+ } else if (_source.getChild("IDREF_array") != null) {
+ _sourceData.stringArray = colladaDOMUtil.parseStringArray(_source.getChild("IDREF_array"));
+ _sourceData.paramType = ParamType.idref_param;
+ }
+
+ // add a hook to our params from the technique_common
+ final Element accessor = getCommonAccessor(_source);
+ if (accessor != null) {
+ if (ColladaInputPipe.logger.isLoggable(Level.FINE)) {
+ ColladaInputPipe.logger.fine("Creating buffers for: " + _source.getAttributeValue("id"));
+ }
+
+ final List<Element> params = accessor.getChildren("param");
+ _paramCount = params.size();
+
+ // Might use this info for real later, but use for testing for unsupported param skipping.
+ boolean skippedParam = false;
+ for (final Element param : params) {
+ final String paramName = param.getAttributeValue("name");
+ if (paramName == null) {
+ skippedParam = true;
+ break;
+ }
+ // String paramType = param.getAttributeValue("type");
+ }
+ if (_paramCount > 1 && skippedParam == true) {
+ ColladaInputPipe.logger.warning("Parameter skipping not yet supported when parsing sources. "
+ + _source.getAttributeValue("id"));
+ }
+
+ _sourceData.count = colladaDOMUtil.getAttributeIntValue(accessor, "count", 0);
+ _sourceData.stride = colladaDOMUtil.getAttributeIntValue(accessor, "stride", 1);
+ _sourceData.offset = colladaDOMUtil.getAttributeIntValue(accessor, "offset", 0);
+ }
+
+ // save our offset
+ _offset = colladaDOMUtil.getAttributeIntValue(input, "offset", 0);
+ _set = colladaDOMUtil.getAttributeIntValue(input, "set", 0);
+
+ _texCoord = 0;
+ }
+
+ public int getOffset() {
+ return _offset;
+ }
+
+ public int getSet() {
+ return _set;
+ }
+
+ public Type getType() {
+ return _type;
+ }
+
+ public SourceData getSourceData() {
+ return _sourceData;
+ }
+
+ public void setupBuffer(final int numEntries, final MeshData meshData, final DataCache cache) {
+ // use our source and the number of params to determine our buffer length
+ // we'll use the params from the common technique accessor:
+ final int size = _paramCount * numEntries;
+ switch (_type) {
+ case POSITION:
+ _buffer = BufferUtils.createFloatBuffer(size);
+ meshData.setVertexCoords(new FloatBufferData(_buffer, _paramCount));
+ break;
+ case NORMAL:
+ _buffer = BufferUtils.createFloatBuffer(size);
+ meshData.setNormalCoords(new FloatBufferData(_buffer, _paramCount));
+ break;
+ case TEXCOORD:
+ _buffer = BufferUtils.createFloatBuffer(size);
+ meshData.setTextureCoords(new FloatBufferData(_buffer, _paramCount), _texCoord);
+ break;
+ case COLOR:
+ _buffer = BufferUtils.createFloatBuffer(size);
+ meshData.setColorCoords(new FloatBufferData(_buffer, _paramCount));
+ cache.getParsedVertexColors().put(meshData, _buffer);
+ break;
+ case TEXTANGENT:
+ _buffer = BufferUtils.createFloatBuffer(size);
+ meshData.setTangentCoords(new FloatBufferData(_buffer, _paramCount));
+ break;
+ // case TEXBINORMAL:
+ // _buffer = BufferUtils.createFloatBuffer(size);
+ // meshData.setTangentBuffer(_buffer);
+ // break;
+ default:
+ }
+ }
+
+ void pushValues(final int memberIndex) {
+ if (_buffer == null) {
+ return;
+ }
+
+ if (_sourceData == null) {
+ throw new ColladaException("No source data found in pipe!", _source);
+ }
+
+ if (memberIndex >= _sourceData.count) {
+ ColladaInputPipe.logger.warning("Accessed invalid index " + memberIndex + " on source " + _source
+ + ". Count: " + _sourceData.count);
+ return;
+ }
+
+ int index = memberIndex * _sourceData.stride + _sourceData.offset;
+ final ParamType paramType = _sourceData.paramType;
+ for (int i = 0; i < _paramCount; i++) {
+ if (ParamType.float_param == paramType) {
+ _buffer.put(_sourceData.floatArray[index]);
+ } else if (ParamType.int_param == paramType) {
+ _buffer.put(_sourceData.intArray[index]);
+ }
+ index++;
+ }
+ }
+
+ public void setTexCoord(final int texCoord) {
+ _texCoord = texCoord;
+ }
+
+ private Element getCommonAccessor(final Element source) {
+ final Element techniqueCommon = source.getChild("technique_common");
+ if (techniqueCommon != null) {
+ return techniqueCommon.getChild("accessor");
+ }
+ return null;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMaterialUtils.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMaterialUtils.java
new file mode 100644
index 0000000..e4572ff
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMaterialUtils.java
@@ -0,0 +1,586 @@
+/**
+ * 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.HashMap;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.jdom2.Element;
+
+import com.ardor3d.extension.model.collada.jdom.data.DataCache;
+import com.ardor3d.extension.model.collada.jdom.data.MaterialInfo;
+import com.ardor3d.extension.model.collada.jdom.data.SamplerTypes;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.ApplyMode;
+import com.ardor3d.image.Texture.CombinerFunctionRGB;
+import com.ardor3d.image.Texture.CombinerOperandRGB;
+import com.ardor3d.image.Texture.CombinerSource;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.MaterialState;
+import com.ardor3d.renderer.state.MaterialState.MaterialFace;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.resource.ResourceSource;
+
+/**
+ * Methods for parsing Collada data related to materials.
+ */
+public class ColladaMaterialUtils {
+ private static final Logger logger = Logger.getLogger(ColladaMaterialUtils.class.getName());
+
+ private final ColladaImporter _importer;
+ private final DataCache _dataCache;
+ private final ColladaDOMUtil _colladaDOMUtil;
+ private final boolean _compressTextures, _loadTextures, _flipTransparency;
+
+ public ColladaMaterialUtils(final ColladaImporter importer, final DataCache dataCache,
+ final ColladaDOMUtil colladaDOMUtil) {
+ _importer = importer;
+ _dataCache = dataCache;
+ _colladaDOMUtil = colladaDOMUtil;
+ _compressTextures = _importer.isCompressTextures();
+ _loadTextures = _importer.isLoadTextures();
+ _flipTransparency = _importer.isFlipTransparency();
+ }
+
+ /**
+ * Find and apply the given material to the given Mesh.
+ *
+ * @param materialName
+ * our material name
+ * @param mesh
+ * the mesh to apply material to.
+ */
+ public void applyMaterial(final String materialName, final Mesh mesh) {
+ if (materialName == null) {
+ logger.warning("materialName is null");
+ return;
+ }
+
+ Element mat = _dataCache.getBoundMaterial(materialName);
+ if (mat == null) {
+ logger.warning("material not bound: " + materialName + ", trying search with id.");
+ mat = _colladaDOMUtil.findTargetWithId(materialName);
+ }
+ if (mat == null || !"material".equals(mat.getName())) {
+ logger.warning("material not found: " + materialName);
+ return;
+ }
+
+ final String originalMaterial = mat.getAttributeValue("id");
+ MaterialInfo mInfo = null;
+ if (!_dataCache.getMaterialInfoMap().containsKey(originalMaterial)) {
+ mInfo = new MaterialInfo();
+ mInfo.setMaterialName(originalMaterial);
+ _dataCache.getMaterialInfoMap().put(originalMaterial, mInfo);
+ }
+ _dataCache.getMeshMaterialMap().put(mesh, originalMaterial);
+
+ final Element child = mat.getChild("instance_effect");
+ final Element effectNode = _colladaDOMUtil.findTargetWithId(child.getAttributeValue("url"));
+ if (effectNode == null) {
+ logger.warning("material effect not found: " + mat.getChild("instance_material").getAttributeValue("url"));
+ return;
+ }
+
+ if ("effect".equals(effectNode.getName())) {
+ /*
+ * temp cache for textures, we do not want to add textures twice (for example, transparant map might point
+ * to diffuse texture)
+ */
+ final HashMap<String, Texture> loadedTextures = new HashMap<String, Texture>();
+ final Element effect = effectNode;
+ // XXX: For now, just grab the common technique:
+ final Element common = effect.getChild("profile_COMMON");
+ if (common != null) {
+ if (mInfo != null) {
+ mInfo.setProfile("COMMON");
+ }
+
+ final Element commonExtra = common.getChild("extra");
+ if (commonExtra != null) {
+ // process with any plugins
+ _importer.readExtra(commonExtra, mesh);
+ }
+
+ final Element technique = common.getChild("technique");
+ String type = "blinn";
+ if (technique.getChild(type) == null) {
+ type = "phong";
+ if (technique.getChild(type) == null) {
+ type = "lambert";
+ if (technique.getChild(type) == null) {
+ type = "constant";
+ if (technique.getChild(type) == null) {
+ ColladaMaterialUtils.logger.warning("COMMON material has unusuable techinque. "
+ + child.getAttributeValue("url"));
+ return;
+ }
+ }
+ }
+ }
+ final Element blinnPhongLambert = technique.getChild(type);
+ if (mInfo != null) {
+ mInfo.setTechnique(type);
+ }
+ final MaterialState mState = new MaterialState();
+
+ // TODO: implement proper transparency handling
+ Texture diffuseTexture = null;
+ ColorRGBA transparent = new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ float transparency = 1.0f;
+ boolean useTransparency = false;
+ String opaqueMode = "A_ONE";
+
+ /*
+ * place holder for current property, we import material properties in fixed order (for texture order)
+ */
+ Element property = null;
+ /* Diffuse property */
+ property = blinnPhongLambert.getChild("diffuse");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("color".equals(propertyValue.getName())) {
+ final ColorRGBA color = _colladaDOMUtil.getColor(propertyValue.getText());
+ mState.setDiffuse(MaterialFace.FrontAndBack, color);
+ } else if ("texture".equals(propertyValue.getName()) && _loadTextures) {
+ diffuseTexture = populateTextureState(mesh, propertyValue, effect, loadedTextures, mInfo,
+ "diffuse");
+ }
+ }
+ /* Ambient property */
+ property = blinnPhongLambert.getChild("ambient");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("color".equals(propertyValue.getName())) {
+ final ColorRGBA color = _colladaDOMUtil.getColor(propertyValue.getText());
+ mState.setAmbient(MaterialFace.FrontAndBack, color);
+ } else if ("texture".equals(propertyValue.getName()) && _loadTextures) {
+ populateTextureState(mesh, propertyValue, effect, loadedTextures, mInfo, "ambient");
+ }
+ }
+ /* Transparent property */
+ property = blinnPhongLambert.getChild("transparent");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("color".equals(propertyValue.getName())) {
+ transparent = _colladaDOMUtil.getColor(propertyValue.getText());
+ // TODO: use this
+ useTransparency = true;
+ } else if ("texture".equals(propertyValue.getName()) && _loadTextures) {
+ populateTextureState(mesh, propertyValue, effect, loadedTextures, mInfo, "transparent");
+ }
+ opaqueMode = property.getAttributeValue("opaque", "A_ONE");
+ }
+ /* Transparency property */
+ property = blinnPhongLambert.getChild("transparency");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("float".equals(propertyValue.getName())) {
+ transparency = Float.parseFloat(propertyValue.getText().replace(",", "."));
+ // TODO: use this
+ if (_flipTransparency) {
+ transparency = 1f - transparency;
+ }
+ useTransparency = true;
+ } else if ("texture".equals(propertyValue.getName()) && _loadTextures) {
+ populateTextureState(mesh, propertyValue, effect, loadedTextures, mInfo, "transparency");
+ }
+ }
+ /* Emission property */
+ property = blinnPhongLambert.getChild("emission");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("color".equals(propertyValue.getName())) {
+ mState.setEmissive(MaterialFace.FrontAndBack, _colladaDOMUtil.getColor(propertyValue.getText()));
+ } else if ("texture".equals(propertyValue.getName()) && _loadTextures) {
+ populateTextureState(mesh, propertyValue, effect, loadedTextures, mInfo, "emissive");
+ }
+ }
+ /* Specular property */
+ property = blinnPhongLambert.getChild("specular");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("color".equals(propertyValue.getName())) {
+ mState.setSpecular(MaterialFace.FrontAndBack, _colladaDOMUtil.getColor(propertyValue.getText()));
+ } else if ("texture".equals(propertyValue.getName()) && _loadTextures) {
+ populateTextureState(mesh, propertyValue, effect, loadedTextures, mInfo, "specular");
+ }
+ }
+ /* Shininess property */
+ property = blinnPhongLambert.getChild("shininess");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("float".equals(propertyValue.getName())) {
+ float shininess = Float.parseFloat(propertyValue.getText().replace(",", "."));
+ if (shininess >= 0.0f && shininess <= 1.0f) {
+ final float oldShininess = shininess;
+ shininess *= 128;
+ logger.finest("Shininess - " + oldShininess
+ + " - was in the [0,1] range. Scaling to [0, 128] - " + shininess);
+ } else if (shininess < 0 || shininess > 128) {
+ final float oldShininess = shininess;
+ shininess = MathUtils.clamp(shininess, 0, 128);
+ logger.warning("Shininess must be between 0 and 128. Shininess " + oldShininess
+ + " was clamped to " + shininess);
+ }
+ mState.setShininess(MaterialFace.FrontAndBack, shininess);
+ } else if ("texture".equals(propertyValue.getName()) && _loadTextures) {
+ populateTextureState(mesh, propertyValue, effect, loadedTextures, mInfo, "shininess");
+ }
+ }
+ /* Reflectivity property */
+ float reflectivity = 1.0f;
+ property = blinnPhongLambert.getChild("reflectivity");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("float".equals(propertyValue.getName())) {
+ reflectivity = Float.parseFloat(propertyValue.getText().replace(",", "."));
+ }
+ }
+ /* Reflective property. Texture only */
+ property = blinnPhongLambert.getChild("reflective");
+ if (property != null) {
+ final Element propertyValue = property.getChildren().get(0);
+ if ("texture".equals(propertyValue.getName()) && _loadTextures) {
+ final Texture reflectiveTexture = populateTextureState(mesh, propertyValue, effect,
+ loadedTextures, mInfo, "reflective");
+
+ reflectiveTexture.setEnvironmentalMapMode(Texture.EnvironmentalMapMode.SphereMap);
+ reflectiveTexture.setApply(ApplyMode.Combine);
+
+ reflectiveTexture.setCombineFuncRGB(CombinerFunctionRGB.Interpolate);
+ // color 1
+ reflectiveTexture.setCombineSrc0RGB(CombinerSource.CurrentTexture);
+ reflectiveTexture.setCombineOp0RGB(CombinerOperandRGB.SourceColor);
+ // color 2
+ reflectiveTexture.setCombineSrc1RGB(CombinerSource.Previous);
+ reflectiveTexture.setCombineOp1RGB(CombinerOperandRGB.SourceColor);
+ // interpolate param will come from alpha of constant color
+ reflectiveTexture.setCombineSrc2RGB(CombinerSource.Constant);
+ reflectiveTexture.setCombineOp2RGB(CombinerOperandRGB.SourceAlpha);
+
+ reflectiveTexture.setConstantColor(new ColorRGBA(1, 1, 1, reflectivity));
+ }
+ }
+
+ /*
+ * An extra tag defines some materials not part of the collada standard. Since we're not able to parse
+ * we simply extract the textures from the element (such that shaders etc can at least pick up on them)
+ */
+ property = technique.getChild("extra");
+ if (property != null) {
+ // process with any plugins
+ if (!_importer.readExtra(property, mesh)) {
+ // no plugin processed our mesh, so process ourselves.
+ getTexturesFromElement(mesh, property, effect, loadedTextures, mInfo);
+ }
+ }
+
+ // XXX: There are some issues with clarity on how to use alpha blending in OpenGL FFP.
+ // The best interpretation I have seen is that if transparent has a texture == diffuse,
+ // Turn on alpha blending and use diffuse alpha.
+
+ // check to make sure we actually need this.
+ // testing separately for a transparency of 0.0 is to hack around erroneous exports, since usually
+ // there is no use in exporting something with 100% transparency.
+ if ("A_ONE".equals(opaqueMode) && ColorRGBA.WHITE.equals(transparent) && transparency == 1.0
+ || transparency == 0.0) {
+ useTransparency = false;
+ }
+
+ if (useTransparency) {
+ if (diffuseTexture != null) {
+ final BlendState blend = new BlendState();
+ blend.setBlendEnabled(true);
+ blend.setTestEnabled(true);
+ blend.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ blend.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ mesh.setRenderState(blend);
+ } else {
+ final BlendState blend = new BlendState();
+ blend.setBlendEnabled(true);
+ blend.setTestEnabled(true);
+ transparent.setAlpha(transparent.getAlpha() * transparency);
+ blend.setConstantColor(transparent);
+ blend.setSourceFunction(BlendState.SourceFunction.ConstantAlpha);
+ blend.setDestinationFunction(BlendState.DestinationFunction.OneMinusConstantAlpha);
+ mesh.setRenderState(blend);
+ }
+
+ mesh.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+ }
+
+ if (mInfo != null) {
+ if (useTransparency) {
+ mInfo.setUseTransparency(useTransparency);
+ if (diffuseTexture == null) {
+ mInfo.setTransparency(transparent.getAlpha() * transparency);
+ }
+ }
+ mInfo.setMaterialState(mState);
+ }
+ mesh.setRenderState(mState);
+ }
+ } else {
+ ColladaMaterialUtils.logger.warning("material effect not found: "
+ + mat.getChild("instance_material").getAttributeValue("url"));
+ }
+ }
+
+ /**
+ * Function to searches an xml node for <texture> elements and adds them to the texture state of the mesh.
+ *
+ * @param mesh
+ * the Ardor3D Mesh to add the Texture to.
+ * @param element
+ * the xml element to start the search on
+ * @param effect
+ * our <instance_effect> element
+ * @param info
+ */
+ private void getTexturesFromElement(final Mesh mesh, final Element element, final Element effect,
+ final HashMap<String, Texture> loadedTextures, final MaterialInfo info) {
+ if ("texture".equals(element.getName()) && _loadTextures) {
+ populateTextureState(mesh, element, effect, loadedTextures, info, null);
+ }
+ @SuppressWarnings("unchecked")
+ final List<Element> children = element.getChildren();
+ if (children != null) {
+ for (final Element child : children) {
+ /* recurse on children */
+ getTexturesFromElement(mesh, child, effect, loadedTextures, info);
+ }
+ }
+ }
+
+ /**
+ * Convert a <texture> element to an Ardor3D representation and store in the given state.
+ *
+ * @param mesh
+ * the Ardor3D Mesh to add the Texture to.
+ * @param daeTexture
+ * our <texture> element
+ * @param effect
+ * our <instance_effect> element
+ * @return the created Texture.
+ */
+ private Texture populateTextureState(final Mesh mesh, final Element daeTexture, final Element effect,
+ final HashMap<String, Texture> loadedTextures, final MaterialInfo info, String textureSlot) {
+ // TODO: Use vert data to determine which texcoords and set to use.
+ // final String uvName = daeTexture.getAttributeValue("texcoord");
+ TextureState tState = (TextureState) mesh.getLocalRenderState(RenderState.StateType.Texture);
+ if (tState == null) {
+ tState = new TextureState();
+ mesh.setRenderState(tState);
+ }
+
+ // Use texture attrib to find correct sampler
+ final String textureReference = daeTexture.getAttributeValue("texture");
+ if (textureSlot == null) {
+ // if we have no texture slot defined (like in the case of an "extra" texture), we'll use the
+ // textureReference.
+ textureSlot = textureReference;
+ }
+
+ /* only add the texture to the state once */
+ if (loadedTextures.containsKey(textureReference)) {
+ final Texture tex = loadedTextures.get(textureReference);
+ if (info != null) {
+ info.setTextureSlot(textureSlot, textureReference, tex, null);
+ }
+ return tex;
+ }
+
+ Element node = _colladaDOMUtil.findTargetWithSid(textureReference);
+ if (node == null) {
+ // Not sure if this is quite right, but spec seems to indicate looking for global id
+ node = _colladaDOMUtil.findTargetWithId("#" + textureReference);
+ }
+
+ if ("newparam".equals(node.getName())) {
+ node = node.getChildren().get(0);
+ }
+
+ Element sampler = null;
+ Element surface = null;
+ Element image = null;
+
+ Texture.MinificationFilter min = Texture.MinificationFilter.BilinearNoMipMaps;
+ if ("sampler2D".equals(node.getName())) {
+ sampler = node;
+ if (sampler.getChild("minfilter") != null) {
+ final String minfilter = sampler.getChild("minfilter").getText();
+ min = Enum.valueOf(SamplerTypes.MinFilterType.class, minfilter).getArdor3dFilter();
+ }
+ // Use sampler to get correct surface
+ node = _colladaDOMUtil.findTargetWithSid(sampler.getChild("source").getText());
+ // node = resolveSid(effect, sampler.getSource());
+ }
+
+ if ("newparam".equals(node.getName())) {
+ node = node.getChildren().get(0);
+ }
+
+ if ("surface".equals(node.getName())) {
+ surface = node;
+ // image(s) will come from surface.
+ } else if ("image".equals(node.getName())) {
+ image = node;
+ }
+
+ // Ok, a few possibilities here...
+ Texture texture = null;
+ String fileName = null;
+ if (surface == null && image != null) {
+ // Only an image found (no sampler). Assume 2d texture. Load.
+ fileName = image.getChild("init_from").getText();
+ texture = loadTexture2D(fileName, min);
+ } else if (surface != null) {
+ // We have a surface, pull images from that.
+ if ("2D".equals(surface.getAttributeValue("type"))) {
+ // look for an init_from with lowest mip and use that. (usually 0)
+
+ // TODO: mip?
+ final Element lowest = surface.getChildren("init_from").get(0);
+ // Element lowest = null;
+ // for (final Element i : (List<Element>) surface.getChildren("init_from")) {
+ // if (lowest == null || lowest.getMip() > i.getMip()) {
+ // lowest = i;
+ // }
+ // }
+
+ if (lowest == null) {
+ logger.warning("surface given with no usable init_from: " + surface);
+ return null;
+ }
+
+ image = _colladaDOMUtil.findTargetWithId("#" + lowest.getText());
+ // image = (DaeImage) root.resolveUrl("#" + lowest.getValue());
+ if (image != null) {
+ fileName = image.getChild("init_from").getText();
+ texture = loadTexture2D(fileName, min);
+ }
+
+ // TODO: add support for mip map levels other than 0.
+ }
+ // TODO: add support for the other texture types.
+ } else {
+ // No surface OR image... warn.
+ logger.warning("texture given with no matching <sampler*> or <image> found.");
+ if (info != null) {
+ info.setTextureSlot(textureSlot, textureReference, null, null);
+ }
+ return null;
+ }
+
+ if (texture != null) {
+ if (sampler != null) {
+ // Apply params from our sampler.
+ applySampler(sampler, texture);
+ }
+
+ // Add to texture state.
+ tState.setTexture(texture, tState.getNumberOfSetTextures());
+ loadedTextures.put(textureReference, texture);
+ if (info != null) {
+ info.setTextureSlot(textureSlot, textureReference, texture, fileName);
+ }
+ } else {
+ logger.warning("unable to load texture: " + daeTexture);
+ if (info != null) {
+ info.setTextureSlot(textureSlot, textureReference, null, fileName);
+ }
+ }
+
+ return texture;
+ }
+
+ private void applySampler(final Element sampler, final Texture texture) {
+ if (sampler.getChild("minfilter") != null) {
+ final String minfilter = sampler.getChild("minfilter").getText();
+ texture.setMinificationFilter(Enum.valueOf(SamplerTypes.MinFilterType.class, minfilter).getArdor3dFilter());
+ }
+ if (sampler.getChild("magfilter") != null) {
+ final String magfilter = sampler.getChild("magfilter").getText();
+ texture.setMagnificationFilter(Enum.valueOf(SamplerTypes.MagFilterType.class, magfilter).getArdor3dFilter());
+ }
+ if (sampler.getChild("wrap_s") != null) {
+ final String wrapS = sampler.getChild("wrap_s").getText();
+ texture.setWrap(Texture.WrapAxis.S, Enum.valueOf(SamplerTypes.WrapModeType.class, wrapS)
+ .getArdor3dWrapMode());
+ }
+ if (sampler.getChild("wrap_t") != null) {
+ final String wrapT = sampler.getChild("wrap_t").getText();
+ texture.setWrap(Texture.WrapAxis.T, Enum.valueOf(SamplerTypes.WrapModeType.class, wrapT)
+ .getArdor3dWrapMode());
+ }
+ if (sampler.getChild("border_color") != null) {
+ texture.setBorderColor(_colladaDOMUtil.getColor(sampler.getChild("border_color").getText()));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void bindMaterials(final Element bindMaterial) {
+ if (bindMaterial == null || bindMaterial.getChildren().isEmpty()) {
+ return;
+ }
+
+ for (final Element instance : bindMaterial.getChild("technique_common").getChildren("instance_material")) {
+ final Element matNode = _colladaDOMUtil.findTargetWithId(instance.getAttributeValue("target"));
+ if (matNode != null && "material".equals(matNode.getName())) {
+ _dataCache.bindMaterial(instance.getAttributeValue("symbol"), matNode);
+ } else {
+ logger.warning("instance material target not found: " + instance.getAttributeValue("target"));
+ }
+
+ // TODO: need to store bound vert data as local data. (also unstore on unbind.)
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void unbindMaterials(final Element bindMaterial) {
+ if (bindMaterial == null || bindMaterial.getChildren().isEmpty()) {
+ return;
+ }
+ for (final Element instance : bindMaterial.getChild("technique_common").getChildren("instance_material")) {
+ _dataCache.unbindMaterial(instance.getAttributeValue("symbol"));
+ }
+ }
+
+ private Texture loadTexture2D(final String path, final Texture.MinificationFilter minFilter) {
+ if (_dataCache.containsTexture(path)) {
+ return _dataCache.getTexture(path);
+ }
+
+ final Texture texture;
+ if (_importer.getTextureLocator() == null) {
+ texture = TextureManager.load(path, minFilter, _compressTextures ? TextureStoreFormat.GuessCompressedFormat
+ : TextureStoreFormat.GuessNoCompressedFormat, true);
+ } else {
+ final ResourceSource source = _importer.getTextureLocator().locateResource(path);
+ texture = TextureManager.load(source, minFilter,
+ _compressTextures ? TextureStoreFormat.GuessCompressedFormat
+ : TextureStoreFormat.GuessNoCompressedFormat, true);
+ }
+ _dataCache.addTexture(path, texture);
+
+ return texture;
+ }
+
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMeshUtils.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMeshUtils.java
new file mode 100644
index 0000000..c04e854
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaMeshUtils.java
@@ -0,0 +1,616 @@
+/**
+ * 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.nio.FloatBuffer;
+import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.logging.Logger;
+
+import org.jdom2.Element;
+
+import com.ardor3d.extension.model.collada.jdom.ColladaInputPipe.Type;
+import com.ardor3d.extension.model.collada.jdom.data.DataCache;
+import com.ardor3d.extension.model.collada.jdom.data.MeshVertPairs;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Point;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.geom.GeometryTool;
+import com.ardor3d.util.geom.GeometryTool.MatchCondition;
+import com.ardor3d.util.geom.VertMap;
+
+/**
+ * Methods for parsing Collada data related to meshes.
+ */
+public class ColladaMeshUtils {
+ private static final Logger logger = Logger.getLogger(ColladaMeshUtils.class.getName());
+
+ private final DataCache _dataCache;
+ private final ColladaDOMUtil _colladaDOMUtil;
+ private final ColladaMaterialUtils _colladaMaterialUtils;
+ private final boolean _optimizeMeshes;
+ private final EnumSet<MatchCondition> _optimizeSettings;
+
+ public ColladaMeshUtils(final DataCache dataCache, final ColladaDOMUtil colladaDOMUtil,
+ final ColladaMaterialUtils colladaMaterialUtils, final boolean optimizeMeshes,
+ final EnumSet<MatchCondition> optimizeSettings) {
+ _dataCache = dataCache;
+ _colladaDOMUtil = colladaDOMUtil;
+ _colladaMaterialUtils = colladaMaterialUtils;
+ _optimizeMeshes = optimizeMeshes;
+ _optimizeSettings = EnumSet.copyOf(optimizeSettings);
+ }
+
+ /**
+ * Builds geometry from an instance_geometry element.
+ *
+ * @param instanceGeometry
+ * @return our Spatial
+ */
+ public Spatial getGeometryMesh(final Element instanceGeometry) {
+ final Element geometry = _colladaDOMUtil.findTargetWithId(instanceGeometry.getAttributeValue("url"));
+
+ if (geometry != null) {
+ return buildMesh(geometry);
+ }
+ return null;
+ }
+
+ /**
+ * Builds a mesh from a Collada geometry element. Currently supported mesh types: mesh, polygons, polylist,
+ * triangles, lines. Not supported yet: linestrips, trifans, tristrips. If no meshtype is found, a pointcloud is
+ * built.
+ *
+ * @param colladaGeometry
+ * @return a Node containing all of the Ardor3D meshes we've parsed from this geometry element.
+ */
+ @SuppressWarnings("unchecked")
+ public Node buildMesh(final Element colladaGeometry) {
+ if (colladaGeometry.getChild("mesh") != null) {
+ final Element cMesh = colladaGeometry.getChild("mesh");
+ final Node meshNode = new Node(colladaGeometry.getAttributeValue("name", colladaGeometry.getName()));
+
+ // Grab all mesh types (polygons, triangles, etc.)
+ // Create each as an Ardor3D Mesh, and attach to node
+ boolean hasChild = false;
+ if (cMesh.getChild("polygons") != null) {
+ for (final Element p : cMesh.getChildren("polygons")) {
+ final Mesh child = buildMeshPolygons(colladaGeometry, p);
+ if (child != null) {
+ if (child.getName() == null) {
+ child.setName(meshNode.getName() + "_polygons");
+ }
+ meshNode.attachChild(child);
+ hasChild = true;
+ }
+ }
+ }
+ if (cMesh.getChild("polylist") != null) {
+ for (final Element p : cMesh.getChildren("polylist")) {
+ final Mesh child = buildMeshPolylist(colladaGeometry, p);
+ if (child != null) {
+ if (child.getName() == null) {
+ child.setName(meshNode.getName() + "_polylist");
+ }
+ meshNode.attachChild(child);
+ hasChild = true;
+ }
+ }
+ }
+ if (cMesh.getChild("triangles") != null) {
+ for (final Element t : cMesh.getChildren("triangles")) {
+ final Mesh child = buildMeshTriangles(colladaGeometry, t);
+ if (child != null) {
+ if (child.getName() == null) {
+ child.setName(meshNode.getName() + "_triangles");
+ }
+ meshNode.attachChild(child);
+ hasChild = true;
+ }
+ }
+ }
+ if (cMesh.getChild("lines") != null) {
+ for (final Element l : cMesh.getChildren("lines")) {
+ final Line child = buildMeshLines(colladaGeometry, l);
+ if (child != null) {
+ if (child.getName() == null) {
+ child.setName(meshNode.getName() + "_lines");
+ }
+ meshNode.attachChild(child);
+ hasChild = true;
+ }
+ }
+ }
+ if (cMesh.getChild("linestrips") != null) {
+ logger.warning("<linestrips> not currently supported.");
+ hasChild = true;
+ // TODO: Add support
+ }
+ if (cMesh.getChild("trifans") != null) {
+ logger.warning("<trifan> not currently supported.");
+ hasChild = true;
+ // TODO: Add support
+ }
+ if (cMesh.getChild("tristrips") != null) {
+ logger.warning("<tristrip> not currently supported.");
+ hasChild = true;
+ // TODO: Add support
+ }
+
+ // If we did not find a valid child, the spec says to add verts as a "cloud of points"
+ if (!hasChild) {
+ logger.warning("No valid child found, creating 'cloud of points'");
+ final Point points = buildPoints(colladaGeometry, cMesh);
+ if (points != null) {
+ if (points.getName() == null) {
+ points.setName(meshNode.getName() + "_points");
+ }
+ meshNode.attachChild(points);
+ }
+ }
+
+ return meshNode;
+ }
+ return null;
+ }
+
+ private Point buildPoints(final Element colladaGeometry, final Element mesh) {
+ if (mesh == null || mesh.getChild("vertices") == null || mesh.getChild("vertices").getChild("input") == null) {
+ return null;
+ }
+ final Point points = new Point();
+ points.setName(mesh.getAttributeValue("name", mesh.getName()));
+
+ // Find POSITION vertices source
+ final Element source = _colladaDOMUtil.getPositionSource(mesh.getChild("vertices"));
+ if (source == null) {
+ return null;
+ }
+
+ if (source.getChild("float_array") != null) {
+ // Turn into Floatbuffer if we have float array data
+ final Element floatArray = source.getChild("float_array");
+ if ("0".equals(floatArray.getAttributeValue("count"))) {
+ return null;
+ }
+ final FloatBuffer vertices = BufferUtils.createFloatBuffer(_colladaDOMUtil.parseFloatArray(floatArray));
+ // Add to points
+ points.getMeshData().setVertexBuffer(vertices);
+ } else if (source.getChild("int_array") != null) {
+ // Turn into Floatbuffer if we have int array data
+ final Element intArray = source.getChild("int_array");
+ if ("0".equals(intArray.getAttributeValue("count"))) {
+ return null;
+ }
+ final int[] data = _colladaDOMUtil.parseIntArray(intArray);
+ final FloatBuffer vertices = BufferUtils.createFloatBuffer(data.length);
+ for (final int i : data) {
+ vertices.put(i);
+ }
+ // Add to points
+ points.getMeshData().setVertexBuffer(vertices);
+ }
+
+ // Add to vert mapping
+ final int[] indices = new int[points.getMeshData().getVertexCount()];
+ for (int i = 0; i < indices.length; i++) {
+ indices[i] = i;
+ }
+ final MeshVertPairs mvp = new MeshVertPairs(points, indices);
+ _dataCache.getVertMappings().put(colladaGeometry, mvp);
+
+ if (_optimizeMeshes) {
+ final VertMap map = GeometryTool.minimizeVerts(points, _optimizeSettings);
+ _dataCache.setMeshVertMap(points, map);
+ }
+
+ // Update bound
+ points.updateModelBound();
+
+ // return
+ return points;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Mesh buildMeshPolygons(final Element colladaGeometry, final Element polys) {
+ if (polys == null || polys.getChild("input") == null) {
+ return null;
+ }
+ final Mesh polyMesh = new Mesh(extractName(colladaGeometry, polys));
+ polyMesh.getMeshData().setIndexMode(IndexMode.Triangles);
+
+ // Build and set RenderStates for our material
+ _colladaMaterialUtils.applyMaterial(polys.getAttributeValue("material"), polyMesh);
+
+ final LinkedList<ColladaInputPipe> pipes = new LinkedList<ColladaInputPipe>();
+ final int maxOffset = extractPipes(polys, pipes);
+ final int interval = maxOffset + 1;
+
+ // use interval & sum of sizes of p entries to determine buffer sizes.
+ int numEntries = 0;
+ int numIndices = 0;
+ for (final Element vals : polys.getChildren("p")) {
+ final int length = _colladaDOMUtil.parseIntArray(vals).length;
+ numEntries += length;
+ numIndices += (length / interval - 2) * 3;
+ }
+ numEntries /= interval;
+
+ // Construct nio buffers for specified inputs.
+ for (final ColladaInputPipe pipe : pipes) {
+ pipe.setupBuffer(numEntries, polyMesh.getMeshData(), _dataCache);
+ }
+
+ // Add to vert mapping
+ final int[] indices = new int[numEntries];
+ final MeshVertPairs mvp = new MeshVertPairs(polyMesh, indices);
+ _dataCache.getVertMappings().put(colladaGeometry, mvp);
+
+ // Prepare indices buffer
+ final IndexBufferData<?> meshIndices = BufferUtils.createIndexBufferData(numIndices, polyMesh.getMeshData()
+ .getVertexCount() - 1);
+ polyMesh.getMeshData().setIndices(meshIndices);
+
+ // go through the polygon entries
+ int firstIndex = 0, vecIndex;
+ final int[] currentVal = new int[interval];
+ for (final Element dia : polys.getChildren("p")) {
+ // for each p, iterate using max offset
+ final int[] vals = _colladaDOMUtil.parseIntArray(dia);
+
+ final int first = firstIndex + 0;
+ System.arraycopy(vals, 0, currentVal, 0, interval);
+ vecIndex = processPipes(pipes, currentVal);
+ if (vecIndex != Integer.MIN_VALUE) {
+ indices[firstIndex + 0] = vecIndex;
+ }
+
+ int prev = firstIndex + 1;
+ System.arraycopy(vals, interval, currentVal, 0, interval);
+ vecIndex = processPipes(pipes, currentVal);
+ if (vecIndex != Integer.MIN_VALUE) {
+ indices[firstIndex + 1] = vecIndex;
+ }
+
+ // first add the first two entries to the buffers.
+
+ // Now go through remaining entries and create a polygon as a triangle fan.
+ for (int j = 2, max = vals.length / interval; j < max; j++) {
+ // add first as index
+ meshIndices.put(first);
+ // add prev as index
+ meshIndices.put(prev);
+
+ // set prev to current
+ prev = firstIndex + j;
+ // add current to buffers
+ System.arraycopy(vals, j * interval, currentVal, 0, interval);
+ vecIndex = processPipes(pipes, currentVal);
+ if (vecIndex != Integer.MIN_VALUE) {
+ indices[firstIndex + j] = vecIndex;
+ }
+ // add current as index
+ meshIndices.put(prev);
+ }
+ firstIndex += vals.length / interval;
+ }
+
+ if (_optimizeMeshes) {
+ final VertMap map = GeometryTool.minimizeVerts(polyMesh, _optimizeSettings);
+ _dataCache.setMeshVertMap(polyMesh, map);
+ }
+
+ // update bounds
+ polyMesh.updateModelBound();
+
+ // return
+ return polyMesh;
+ }
+
+ public Mesh buildMeshPolylist(final Element colladaGeometry, final Element polys) {
+ if (polys == null || polys.getChild("input") == null) {
+ return null;
+ }
+ final Mesh polyMesh = new Mesh(extractName(colladaGeometry, polys));
+ polyMesh.getMeshData().setIndexMode(IndexMode.Triangles);
+
+ // Build and set RenderStates for our material
+ _colladaMaterialUtils.applyMaterial(polys.getAttributeValue("material"), polyMesh);
+
+ final LinkedList<ColladaInputPipe> pipes = new LinkedList<ColladaInputPipe>();
+ final int maxOffset = extractPipes(polys, pipes);
+ final int interval = maxOffset + 1;
+
+ // use interval & sum of sizes of vcount to determine buffer sizes.
+ int numEntries = 0;
+ int numIndices = 0;
+ for (final int length : _colladaDOMUtil.parseIntArray(polys.getChild("vcount"))) {
+ numEntries += length;
+ numIndices += (length - 2) * 3;
+ }
+
+ // Construct nio buffers for specified inputs.
+ for (final ColladaInputPipe pipe : pipes) {
+ pipe.setupBuffer(numEntries, polyMesh.getMeshData(), _dataCache);
+ }
+
+ // Add to vert mapping
+ final int[] indices = new int[numEntries];
+ final MeshVertPairs mvp = new MeshVertPairs(polyMesh, indices);
+ _dataCache.getVertMappings().put(colladaGeometry, mvp);
+
+ // Prepare indices buffer
+ final IndexBufferData<?> meshIndices = BufferUtils.createIndexBufferData(numIndices, polyMesh.getMeshData()
+ .getVertexCount() - 1);
+ polyMesh.getMeshData().setIndices(meshIndices);
+
+ // go through the polygon entries
+ int firstIndex = 0;
+ int vecIndex;
+ final int[] vals = _colladaDOMUtil.parseIntArray(polys.getChild("p"));
+ for (final int length : _colladaDOMUtil.parseIntArray(polys.getChild("vcount"))) {
+ final int[] currentVal = new int[interval];
+
+ // first add the first two entries to the buffers.
+ final int first = firstIndex + 0;
+ System.arraycopy(vals, (first * interval), currentVal, 0, interval);
+ vecIndex = processPipes(pipes, currentVal);
+ if (vecIndex != Integer.MIN_VALUE) {
+ indices[firstIndex + 0] = vecIndex;
+ }
+
+ int prev = firstIndex + 1;
+ System.arraycopy(vals, (prev * interval), currentVal, 0, interval);
+ vecIndex = processPipes(pipes, currentVal);
+ if (vecIndex != Integer.MIN_VALUE) {
+ indices[firstIndex + 1] = vecIndex;
+ }
+
+ // Now go through remaining entries and create a polygon as a triangle fan.
+ for (int j = 2, max = length; j < max; j++) {
+ // add first as index
+ meshIndices.put(first);
+ // add prev as index
+ meshIndices.put(prev);
+
+ // set prev to current
+ prev = firstIndex + j;
+ // add current to buffers
+ System.arraycopy(vals, (prev * interval), currentVal, 0, interval);
+ vecIndex = processPipes(pipes, currentVal);
+ if (vecIndex != Integer.MIN_VALUE) {
+ indices[firstIndex + j] = vecIndex;
+ }
+ // add current as index
+ meshIndices.put(prev);
+ }
+ firstIndex += length;
+ }
+
+ if (_optimizeMeshes) {
+ final VertMap map = GeometryTool.minimizeVerts(polyMesh, _optimizeSettings);
+ _dataCache.setMeshVertMap(polyMesh, map);
+ }
+
+ // update bounds
+ polyMesh.updateModelBound();
+
+ // return
+ return polyMesh;
+ }
+
+ public Mesh buildMeshTriangles(final Element colladaGeometry, final Element tris) {
+ if (tris == null || tris.getChild("input") == null || tris.getChild("p") == null) {
+ return null;
+ }
+ final Mesh triMesh = new Mesh(extractName(colladaGeometry, tris));
+ triMesh.getMeshData().setIndexMode(IndexMode.Triangles);
+
+ // Build and set RenderStates for our material
+ _colladaMaterialUtils.applyMaterial(tris.getAttributeValue("material"), triMesh);
+
+ final LinkedList<ColladaInputPipe> pipes = new LinkedList<ColladaInputPipe>();
+ final int maxOffset = extractPipes(tris, pipes);
+ final int interval = maxOffset + 1;
+
+ // use interval & size of p array to determine buffer sizes.
+ final int[] vals = _colladaDOMUtil.parseIntArray(tris.getChild("p"));
+ final int numEntries = vals.length / interval;
+
+ // Construct nio buffers for specified inputs.
+ for (final ColladaInputPipe pipe : pipes) {
+ pipe.setupBuffer(numEntries, triMesh.getMeshData(), _dataCache);
+ }
+
+ // Add to vert mapping
+ final int[] indices = new int[numEntries];
+ final MeshVertPairs mvp = new MeshVertPairs(triMesh, indices);
+ _dataCache.getVertMappings().put(colladaGeometry, mvp);
+
+ // go through the p entry
+ // for each p, iterate using max offset
+ final int[] currentVal = new int[interval];
+
+ // Go through entries and add to buffers.
+ for (int j = 0, max = numEntries; j < max; j++) {
+ // add entry to buffers
+ System.arraycopy(vals, j * interval, currentVal, 0, interval);
+ final int rVal = processPipes(pipes, currentVal);
+ if (rVal != Integer.MIN_VALUE) {
+ indices[j] = rVal;
+ }
+ }
+
+ if (_optimizeMeshes) {
+ final VertMap map = GeometryTool.minimizeVerts(triMesh, _optimizeSettings);
+ _dataCache.setMeshVertMap(triMesh, map);
+ }
+
+ triMesh.updateModelBound();
+
+ return triMesh;
+ }
+
+ public Line buildMeshLines(final Element colladaGeometry, final Element lines) {
+ if (lines == null || lines.getChild("input") == null || lines.getChild("p") == null) {
+ return null;
+ }
+ final Line lineMesh = new Line(extractName(colladaGeometry, lines));
+
+ // Build and set RenderStates for our material
+ _colladaMaterialUtils.applyMaterial(lines.getAttributeValue("material"), lineMesh);
+
+ final LinkedList<ColladaInputPipe> pipes = new LinkedList<ColladaInputPipe>();
+ final int maxOffset = extractPipes(lines, pipes);
+ final int interval = maxOffset + 1;
+
+ // use interval & size of p array to determine buffer sizes.
+ final int[] vals = _colladaDOMUtil.parseIntArray(lines.getChild("p"));
+ final int numEntries = vals.length / interval;
+
+ // Construct nio buffers for specified inputs.
+ for (final ColladaInputPipe pipe : pipes) {
+ pipe.setupBuffer(numEntries, lineMesh.getMeshData(), _dataCache);
+ }
+
+ // Add to vert mapping
+ final int[] indices = new int[numEntries];
+ final MeshVertPairs mvp = new MeshVertPairs(lineMesh, indices);
+ _dataCache.getVertMappings().put(colladaGeometry, mvp);
+
+ // go through the p entry
+ // for each p, iterate using max offset
+ final int[] currentVal = new int[interval];
+
+ // Go through entries and add to buffers.
+ for (int j = 0, max = numEntries; j < max; j++) {
+ // add entry to buffers
+ System.arraycopy(vals, j * interval, currentVal, 0, interval);
+ final int rVal = processPipes(pipes, currentVal);
+ if (rVal != Integer.MIN_VALUE) {
+ indices[j] = rVal;
+ }
+ }
+
+ if (_optimizeMeshes) {
+ final VertMap map = GeometryTool.minimizeVerts(lineMesh, _optimizeSettings);
+ _dataCache.setMeshVertMap(lineMesh, map);
+ }
+
+ lineMesh.updateModelBound();
+
+ return lineMesh;
+ }
+
+ /**
+ * Extract our pipes from the given parent element.
+ *
+ * @param inputsParent
+ * @param pipesStore
+ * the store for our pipes
+ * @return the max offset of our pipes.
+ */
+ @SuppressWarnings("unchecked")
+ private int extractPipes(final Element inputsParent, final LinkedList<ColladaInputPipe> pipesStore) {
+ int maxOffset = 0;
+ int texCount = 0;
+ for (final Element input : inputsParent.getChildren("input")) {
+ maxOffset = Math.max(maxOffset, _colladaDOMUtil.getAttributeIntValue(input, "offset", 0));
+ try {
+ final Type type = Type.valueOf(input.getAttributeValue("semantic"));
+ if (type == Type.VERTEX) {
+ final Element vertexElement = _colladaDOMUtil.findTargetWithId(input.getAttributeValue("source"));
+ for (final Element vertexInput : vertexElement.getChildren("input")) {
+ vertexInput.setAttribute("offset", input.getAttributeValue("offset"));
+ vertexInput.setAttribute("isVertexDefined", "true");
+ final ColladaInputPipe pipe = new ColladaInputPipe(_colladaDOMUtil, vertexInput);
+ if (pipe.getType() == Type.TEXCOORD) {
+ pipe.setTexCoord(texCount++);
+ }
+ pipesStore.add(pipe);
+ }
+ } else {
+ final ColladaInputPipe pipe = new ColladaInputPipe(_colladaDOMUtil, input);
+ if (pipe.getType() == Type.TEXCOORD) {
+ pipe.setTexCoord(texCount++);
+ }
+ pipesStore.add(pipe);
+ }
+ } catch (final Exception ex) {
+ logger.warning("Unknown input type: " + input.getAttributeValue("semantic"));
+ continue;
+ }
+ }
+ return maxOffset;
+ }
+
+ /**
+ * Push the values at the given indices of currentVal onto the buffers defined in pipes.
+ *
+ * @param pipes
+ * @param currentVal
+ * @return the vertex index referenced in the given indices based on the pipes. Integer.MIN_VALUE is returned if no
+ * vertex pipe is found.
+ */
+ private int processPipes(final LinkedList<ColladaInputPipe> pipes, final int[] currentVal) {
+ // go through our pipes. use the indices in currentVal to pull the correct float val
+ // from our source and set into our buffer.
+ int rVal = Integer.MIN_VALUE;
+ for (final ColladaInputPipe pipe : pipes) {
+ pipe.pushValues(currentVal[pipe.getOffset()]);
+ if (pipe.getType() == Type.POSITION) {
+ rVal = currentVal[pipe.getOffset()];
+ }
+ }
+ return rVal;
+ }
+
+ /**
+ * Extract name from xml element, some exporters don't support 'name' attribute, so we better use the material
+ * instead of a generic name.
+ *
+ * @param element
+ * @return value from 'name' or 'material' attribute
+ */
+ private String extractName(final Element colladaGeometry, final Element element) {
+ // Try to get mesh name
+ String name = element.getAttributeValue("name");
+ if (name == null || name.isEmpty()) {
+ // No mesh name found, try to get mesh id
+ name = element.getAttributeValue("id");
+ }
+ if (name == null || name.isEmpty()) {
+ // No mesh name or id found, try to get parent geometry name
+ name = colladaGeometry.getAttributeValue("name");
+ if (name == null || name.isEmpty()) {
+ // No parent geometry name found, try to get geometry id (mandatory according to spec)
+ name = colladaGeometry.getAttributeValue("id");
+ }
+ if (name == null) {
+ name = "";
+ }
+
+ // Since we have retrieved the parent geometry name/id, we append the material(if any),
+ // to make identification unique.
+ final String materialName = element.getAttributeValue("material");
+ if (materialName != null && !materialName.isEmpty()) {
+ name += "[" + materialName + "]";
+ }
+ }
+
+ return name;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaNodeUtils.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaNodeUtils.java
new file mode 100644
index 0000000..da4535e
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaNodeUtils.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 <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.model.collada.jdom;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.jdom2.Element;
+
+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
+ final List<List<Joint>> jointCollection = Lists.newArrayList();
+ for (final JointNode jointChildNode : _dataCache.getRootJointNode().getChildren()) {
+ final List<Joint> jointList = Lists.newArrayList();
+ buildJointLists(jointChildNode, jointList);
+ jointCollection.add(jointList);
+ }
+
+ // build a skeleton for each joint list.
+ for (final List<Joint> jointList : jointCollection) {
+ final Joint[] joints = jointList.toArray(new Joint[jointList.size()]);
+ final Skeleton skeleton = new Skeleton(joints[0].getName() + "_skeleton", joints);
+ logger.fine(skeleton.getName());
+ for (final Joint joint : jointList) {
+ _dataCache.getJointSkeletonMapping().put(joint, skeleton);
+ 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) {
+ NodeType nodeType = NodeType.NODE;
+ if (dNode.getAttribute("type") != null) {
+ nodeType = Enum.valueOf(NodeType.class, dNode.getAttributeValue("type"));
+ }
+ 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);
+ }
+ }
+
+ // process any concrete child nodes.
+ for (final Element n : dNode.getChildren("node")) {
+ final Node subNode = buildNode(n, jointNode);
+ if (subNode != null) {
+ node.attachChild(subNode);
+ }
+ }
+
+ // Cache reference
+ _dataCache.getElementSpatialMapping().put(dNode, node);
+
+ return 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);
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AnimationItem.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AnimationItem.java
new file mode 100644
index 0000000..43c732b
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AnimationItem.java
@@ -0,0 +1,94 @@
+/**
+ * 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.data;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.extension.animation.skeletal.clip.AnimationClip;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.google.common.collect.Lists;
+
+@SavableFactory(factoryMethod = "initSavable")
+public class AnimationItem implements Savable {
+ private final String _name;
+ private final List<AnimationItem> _children = Lists.newArrayList();
+ private AnimationClip _animationClip;
+
+ public AnimationItem(final String name) {
+ _name = name;
+ }
+
+ public AnimationClip getAnimationClip() {
+ return _animationClip;
+ }
+
+ public void setAnimationClip(final AnimationClip animationClip) {
+ _animationClip = animationClip;
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public List<AnimationItem> getChildren() {
+ return _children;
+ }
+
+ @Override
+ public String toString() {
+ return "AnimationItem [name=" + _name + (_animationClip != null ? ", " + _animationClip.toString() : "") + "]";
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ @Override
+ public Class<? extends AnimationItem> getClassTag() {
+ return this.getClass();
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ final String name = capsule.readString("name", "");
+ try {
+ final Field field1 = AnimationClip.class.getDeclaredField("_name");
+ field1.setAccessible(true);
+ field1.set(this, name);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ _children.clear();
+ _children.addAll(capsule.readSavableList("children", new ArrayList<AnimationItem>()));
+ _animationClip = (AnimationClip) capsule.readSavable("animationClip", null);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_name, "name", null);
+ capsule.writeSavableList(_children, "children", null);
+ capsule.write(_animationClip, "animationClip", null);
+ }
+
+ public static AnimationItem initSavable() {
+ return new AnimationItem();
+ }
+
+ private AnimationItem() {
+ this(null);
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AssetData.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AssetData.java
new file mode 100644
index 0000000..9e53f2c
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/AssetData.java
@@ -0,0 +1,201 @@
+/**
+ * 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.data;
+
+import java.io.IOException;
+
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * Holds data related to asset info.
+ */
+public class AssetData implements Savable {
+
+ private String author;
+ private String authoringTool;
+ private String comments;
+ private String copyright;
+ private String sourceData;
+ private String created;
+ private String keywords;
+ private String modified;
+ private String revision;
+ private String subject;
+ private String title;
+ private String unitName;
+ private float unitMeter;
+ private ReadOnlyVector3 upAxis;
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(final String author) {
+ this.author = author;
+ }
+
+ public String getAuthoringTool() {
+ return authoringTool;
+ }
+
+ public void setAuthoringTool(final String authoringTool) {
+ this.authoringTool = authoringTool;
+ }
+
+ public String getComments() {
+ return comments;
+ }
+
+ public void setComments(final String comments) {
+ this.comments = comments;
+ }
+
+ public String getCopyright() {
+ return copyright;
+ }
+
+ public void setCopyright(final String copyright) {
+ this.copyright = copyright;
+ }
+
+ public String getSourceData() {
+ return sourceData;
+ }
+
+ public void setSourceData(final String sourceData) {
+ this.sourceData = sourceData;
+ }
+
+ public String getCreated() {
+ return created;
+ }
+
+ public void setCreated(final String created) {
+ this.created = created;
+ }
+
+ public String getKeywords() {
+ return keywords;
+ }
+
+ public void setKeywords(final String keywords) {
+ this.keywords = keywords;
+ }
+
+ public String getModified() {
+ return modified;
+ }
+
+ public void setModified(final String modified) {
+ this.modified = modified;
+ }
+
+ public String getRevision() {
+ return revision;
+ }
+
+ public void setRevision(final String revision) {
+ this.revision = revision;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(final String subject) {
+ this.subject = subject;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(final String title) {
+ this.title = title;
+ }
+
+ public String getUnitName() {
+ return unitName;
+ }
+
+ public void setUnitName(final String unitName) {
+ this.unitName = unitName;
+ }
+
+ public float getUnitMeter() {
+ return unitMeter;
+ }
+
+ public void setUnitMeter(final float unitMeter) {
+ this.unitMeter = unitMeter;
+ }
+
+ /**
+ * @return the up axis as defined in the &lt;asset> tag, or null if not existing.
+ */
+ public ReadOnlyVector3 getUpAxis() {
+ return upAxis;
+ }
+
+ public void setUpAxis(final ReadOnlyVector3 upAxis) {
+ this.upAxis = upAxis;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ @Override
+ public Class<? extends AssetData> getClassTag() {
+ return getClass();
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ author = capsule.readString("author", null);
+ authoringTool = capsule.readString("authoringTool", null);
+ comments = capsule.readString("comments", null);
+ copyright = capsule.readString("copyright", null);
+ sourceData = capsule.readString("sourceData", null);
+ created = capsule.readString("created", null);
+ keywords = capsule.readString("keywords", null);
+ modified = capsule.readString("modified", null);
+ revision = capsule.readString("revision", null);
+ subject = capsule.readString("subject", null);
+ title = capsule.readString("title", null);
+ unitName = capsule.readString("unitName", null);
+ unitMeter = capsule.readFloat("unitMeter", 0f);
+ upAxis = (ReadOnlyVector3) capsule.readSavable("upAxis", null);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(author, "author", null);
+ capsule.write(authoringTool, "authoringTool", null);
+ capsule.write(comments, "comments", null);
+ capsule.write(copyright, "copyright", null);
+ capsule.write(sourceData, "sourceData", null);
+ capsule.write(created, "created", null);
+ capsule.write(keywords, "keywords", null);
+ capsule.write(modified, "modified", null);
+ capsule.write(revision, "revision", null);
+ capsule.write(subject, "subject", null);
+ capsule.write(title, "title", null);
+ capsule.write(unitName, "unitName", null);
+ capsule.write(unitMeter, "unitMeter", 0f);
+ if (upAxis instanceof Savable) {
+ capsule.write((Savable) upAxis, "upAxis", null);
+ }
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ColladaStorage.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ColladaStorage.java
new file mode 100644
index 0000000..8eafa20
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ColladaStorage.java
@@ -0,0 +1,161 @@
+/**
+ * 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.data;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.extension.animation.skeletal.clip.AbstractAnimationChannel;
+import com.ardor3d.extension.animation.skeletal.clip.AnimationClip;
+import com.ardor3d.extension.animation.skeletal.clip.TransformChannel;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+/**
+ * Data storage object meant to hold objects parsed from a Collada file that the user might want to directly access.
+ */
+public class ColladaStorage implements Savable {
+
+ private Node _scene;
+ private final List<SkinData> _skins = Lists.newArrayList();
+ private AssetData _assetData;
+
+ private final List<AbstractAnimationChannel> _transformChannels = Lists.newArrayList();
+ private AnimationItem _animationItemRoot;
+
+ // List of parsed color buffers, useful if collada includes multiple color channels per meshdata object
+ // Transient because the key is identity - meshdata.
+ private transient Multimap<MeshData, FloatBuffer> _parsedVertexColors;
+
+ // Map of parsed material information, useful for doing post-manipulation of model information.
+ // Transient because the key is identity - mesh.
+ private transient Map<Mesh, String> _meshMaterialInfo;
+ private transient Map<String, MaterialInfo> _materialMap;
+
+ public void setScene(final Node scene) {
+ _scene = scene;
+ }
+
+ /**
+ * @return a Node representing the parsed "visual scene".
+ */
+ public Node getScene() {
+ return _scene;
+ }
+
+ /**
+ * @return a list of data objects representing each <skin> tag parsed during reading of the visual scene.
+ */
+ public List<SkinData> getSkins() {
+ return _skins;
+ }
+
+ public AssetData getAssetData() {
+ return _assetData;
+ }
+
+ public void setAssetData(final AssetData assetData) {
+ _assetData = assetData;
+ }
+
+ public List<AbstractAnimationChannel> getAnimationChannels() {
+ return _transformChannels;
+ }
+
+ /**
+ * @return the root of our animation library. We can use this to walk through all animations in the Collada file.
+ */
+ public AnimationItem getAnimationItemRoot() {
+ return _animationItemRoot;
+ }
+
+ public void setAnimationItemRoot(final AnimationItem animationItemRoot) {
+ _animationItemRoot = animationItemRoot;
+ }
+
+ /**
+ * Extract all animation channels in the Collada file as a single, unified AnimationClip.
+ *
+ * @param name
+ * the name to give our new clip.
+ * @return the new AnimationClip.
+ */
+ public AnimationClip extractChannelsAsClip(final String name) {
+ final AnimationClip clip = new AnimationClip(name);
+ for (final AbstractAnimationChannel channel : getAnimationChannels()) {
+ clip.addChannel(channel);
+ }
+ return clip;
+ }
+
+ /**
+ * @return a transient Multimap of MeshData -> List of parsed vertex colors. Only MeshData objects that had multiple
+ * vertex colors parsed will show up in this map.
+ */
+ public Multimap<MeshData, FloatBuffer> getParsedVertexColors() {
+ return _parsedVertexColors;
+ }
+
+ public void setParsedVertexColors(final Multimap<MeshData, FloatBuffer> parsedVertexColors) {
+ _parsedVertexColors = parsedVertexColors;
+ }
+
+ public Map<String, MaterialInfo> getMaterialMap() {
+ return _materialMap;
+ }
+
+ public void setMaterialMap(final Map<String, MaterialInfo> materialMap) {
+ _materialMap = materialMap;
+ }
+
+ public Map<Mesh, String> getMeshMaterialInfo() {
+ return _meshMaterialInfo;
+ }
+
+ public void setMeshMaterialInfo(final Map<Mesh, String> meshMaterialInfo) {
+ _meshMaterialInfo = meshMaterialInfo;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ @Override
+ public Class<?> getClassTag() {
+ return this.getClass();
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ _assetData = (AssetData) capsule.readSavable("assetData", null);
+ _scene = (Node) capsule.readSavable("scene", null);
+ _skins.addAll(capsule.readSavableList("skins", new LinkedList<SkinData>()));
+ _transformChannels.clear();
+ _transformChannels.addAll(capsule.readSavableList("jointChannels", new LinkedList<TransformChannel>()));
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_assetData, "assetData", null);
+ capsule.write(_scene, "scene", null);
+ capsule.writeSavableList(_skins, "skins", new LinkedList<SkinData>());
+ capsule.writeSavableList(_transformChannels, "jointChannels", new LinkedList<TransformChannel>());
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ControllerStore.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ControllerStore.java
new file mode 100644
index 0000000..b827add
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/ControllerStore.java
@@ -0,0 +1,16 @@
+
+package com.ardor3d.extension.model.collada.jdom.data;
+
+import org.jdom2.Element;
+
+import com.ardor3d.scenegraph.Node;
+
+public class ControllerStore {
+ public final Node ardorParentNode;
+ public final Element instanceController;
+
+ public ControllerStore(final Node ardorParentNode, final Element instanceController) {
+ this.ardorParentNode = ardorParentNode;
+ this.instanceController = instanceController;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/DataCache.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/DataCache.java
new file mode 100644
index 0000000..2ae984f
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/DataCache.java
@@ -0,0 +1,229 @@
+/**
+ * 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.data;
+
+import java.nio.FloatBuffer;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.jdom2.Element;
+import org.jdom2.xpath.XPath;
+
+import com.ardor3d.extension.animation.skeletal.Joint;
+import com.ardor3d.extension.animation.skeletal.Skeleton;
+import com.ardor3d.extension.animation.skeletal.SkeletonPose;
+import com.ardor3d.image.Texture;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Spatial;
+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;
+
+/**
+ * Performance cache and temp storage during parsing.
+ */
+public class DataCache {
+ private final Map<String, Element> _boundMaterials;
+ private final Map<String, Texture> _textures;
+ private final Map<String, Element> _idCache;
+ private final Map<String, Element> _sidCache;
+ private final Map<String, XPath> _xPathExpressions;
+ private final Pattern _pattern;
+ private final List<String> _transformTypes;
+
+ private final Map<Element, float[]> _floatArrays;
+ private final Map<Element, double[]> _doubleArrays;
+ private final Map<Element, boolean[]> _booleanArrays;
+ private final Map<Element, int[]> _intArrays;
+ private final Map<Element, String[]> _stringArrays;
+
+ private final Multimap<Element, MeshVertPairs> _vertMappings;
+ private final Map<Mesh, VertMap> _meshVertMap;
+ private final Multimap<MeshData, FloatBuffer> _parsedVertexColors;
+ private final Map<String, MaterialInfo> _materialInfoMap;
+ private final Map<Mesh, String> _meshMaterialMap;
+ private final Map<Element, Spatial> _elementSpatialMapping;
+
+ private final Map<Element, Joint> _elementJointMapping;
+ private final Map<String, Joint> _externalJointMapping;
+ private JointNode _rootJointNode;
+ private final Map<Joint, Skeleton> _jointSkeletonMapping;
+ private final Map<Skeleton, SkeletonPose> _skeletonPoseMapping;
+ private final List<Skeleton> _skeletons;
+ private final List<ControllerStore> _controllers;
+
+ public DataCache() {
+ _boundMaterials = Maps.newHashMap();
+ _textures = Maps.newHashMap();
+ _idCache = Maps.newHashMap();
+ _sidCache = Maps.newHashMap();
+ _xPathExpressions = Maps.newHashMap();
+ _pattern = Pattern.compile("\\s");
+
+ _transformTypes = Collections.unmodifiableList(Lists.newArrayList("lookat", "matrix", "rotate", "scale",
+ "scew", "translate"));
+
+ _floatArrays = Maps.newHashMap();
+ _doubleArrays = Maps.newHashMap();
+ _booleanArrays = Maps.newHashMap();
+ _intArrays = Maps.newHashMap();
+ _stringArrays = Maps.newHashMap();
+ _vertMappings = ArrayListMultimap.create();
+ _meshVertMap = Maps.newIdentityHashMap();
+ _parsedVertexColors = ArrayListMultimap.create();
+ _materialInfoMap = Maps.newHashMap();
+ _meshMaterialMap = Maps.newIdentityHashMap();
+
+ _elementSpatialMapping = Maps.newHashMap();
+
+ _elementJointMapping = Maps.newHashMap();
+ _externalJointMapping = Maps.newHashMap();
+ _skeletons = Lists.newArrayList();
+ _jointSkeletonMapping = Maps.newHashMap();
+ _skeletonPoseMapping = Maps.newHashMap();
+ _controllers = Lists.newArrayList();
+ }
+
+ public void bindMaterial(final String ref, final Element material) {
+ if (!_boundMaterials.containsKey(ref)) {
+ _boundMaterials.put(ref, material);
+ }
+ }
+
+ public void unbindMaterial(final String ref) {
+ _boundMaterials.remove(ref);
+ }
+
+ public Element getBoundMaterial(final String ref) {
+ return _boundMaterials.get(ref);
+ }
+
+ public boolean containsTexture(final String path) {
+ return _textures.containsKey(path);
+ }
+
+ public void addTexture(final String path, final Texture texture) {
+ _textures.put(path, texture);
+ }
+
+ public Texture getTexture(final String path) {
+ return _textures.get(path);
+ }
+
+ public Map<String, Element> getIdCache() {
+ return _idCache;
+ }
+
+ public Map<String, Element> getSidCache() {
+ return _sidCache;
+ }
+
+ public Map<String, XPath> getxPathExpressions() {
+ return _xPathExpressions;
+ }
+
+ public Pattern getPattern() {
+ return _pattern;
+ }
+
+ public List<String> getTransformTypes() {
+ return _transformTypes;
+ }
+
+ public Map<Element, float[]> getFloatArrays() {
+ return _floatArrays;
+ }
+
+ public Map<Element, double[]> getDoubleArrays() {
+ return _doubleArrays;
+ }
+
+ public Map<Element, boolean[]> getBooleanArrays() {
+ return _booleanArrays;
+ }
+
+ public Map<Element, int[]> getIntArrays() {
+ return _intArrays;
+ }
+
+ public Map<Element, String[]> getStringArrays() {
+ return _stringArrays;
+ }
+
+ public Multimap<Element, MeshVertPairs> getVertMappings() {
+ return _vertMappings;
+ }
+
+ public Map<Mesh, VertMap> getMeshVertMap() {
+ return _meshVertMap;
+ }
+
+ public Multimap<MeshData, FloatBuffer> getParsedVertexColors() {
+ return _parsedVertexColors;
+ }
+
+ public Map<String, MaterialInfo> getMaterialInfoMap() {
+ return _materialInfoMap;
+ }
+
+ public Map<Mesh, String> getMeshMaterialMap() {
+ return _meshMaterialMap;
+ }
+
+ public Map<Element, Spatial> getElementSpatialMapping() {
+ return _elementSpatialMapping;
+ }
+
+ public Map<Element, Joint> getElementJointMapping() {
+ return _elementJointMapping;
+ }
+
+ public Map<String, Joint> getExternalJointMapping() {
+ return _externalJointMapping;
+ }
+
+ public JointNode getRootJointNode() {
+ return _rootJointNode;
+ }
+
+ public void setRootJointNode(final JointNode rootJointNode) {
+ _rootJointNode = rootJointNode;
+ }
+
+ public Map<Joint, Skeleton> getJointSkeletonMapping() {
+ return _jointSkeletonMapping;
+ }
+
+ public Map<Skeleton, SkeletonPose> getSkeletonPoseMapping() {
+ return _skeletonPoseMapping;
+ }
+
+ public List<ControllerStore> getControllers() {
+ return _controllers;
+ }
+
+ public List<Skeleton> getSkeletons() {
+ return _skeletons;
+ }
+
+ public void addSkeleton(final Skeleton skeleton) {
+ _skeletons.add(skeleton);
+ }
+
+ public void setMeshVertMap(final Mesh geometry, final VertMap map) {
+ _meshVertMap.put(geometry, map);
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/JointNode.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/JointNode.java
new file mode 100644
index 0000000..66a8c75
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/JointNode.java
@@ -0,0 +1,54 @@
+/**
+ * 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.data;
+
+import java.util.List;
+
+import com.ardor3d.extension.animation.skeletal.Joint;
+import com.ardor3d.scenegraph.Node;
+import com.google.common.collect.Lists;
+
+public class JointNode {
+ private JointNode parent;
+ private final List<JointNode> children = Lists.newArrayList();
+ private final Joint joint;
+
+ /** Scene node associated with the Joint. */
+ private Node sceneNode;
+
+ public JointNode(final Joint joint) {
+ this.joint = joint;
+ }
+
+ public List<JointNode> getChildren() {
+ return children;
+ }
+
+ public Joint getJoint() {
+ return joint;
+ }
+
+ public JointNode getParent() {
+ return parent;
+ }
+
+ public void setParent(final JointNode parent) {
+ this.parent = parent;
+ }
+
+ public Node getSceneNode() {
+ return sceneNode;
+ }
+
+ public void setSceneNode(final Node node) {
+ sceneNode = node;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MaterialInfo.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MaterialInfo.java
new file mode 100644
index 0000000..91c918d
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MaterialInfo.java
@@ -0,0 +1,98 @@
+/**
+ * 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.data;
+
+import java.util.Map;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.renderer.state.MaterialState;
+import com.google.common.collect.Maps;
+
+public class MaterialInfo {
+
+ private String _materialName;
+ private String _profile;
+ private String _technique;
+ private final Map<String, String> _textureReferences = Maps.newHashMap();
+ private final Map<String, Texture> _textures = Maps.newHashMap();
+ private final Map<String, String> _textureFileNames = Maps.newHashMap();
+ private boolean _useTransparency;
+ private float _transparency = 1.0f;
+
+ private MaterialState _materialState;
+
+ public void setMaterialName(final String materialName) {
+ _materialName = materialName;
+ }
+
+ public String getMaterialName() {
+ return _materialName;
+ }
+
+ public void setProfile(final String profile) {
+ _profile = profile;
+ }
+
+ public String getProfile() {
+ return _profile;
+ }
+
+ public void setTechnique(final String technique) {
+ _technique = technique;
+ }
+
+ public String getTechnique() {
+ return _technique;
+ }
+
+ public void setTextureSlot(final String textureSlot, final String textureReference, final Texture texture,
+ final String fileName) {
+ _textureReferences.put(textureSlot, textureReference);
+ _textures.put(textureSlot, texture);
+ _textureFileNames.put(textureSlot, fileName);
+ }
+
+ public Map<String, String> getTextureReferences() {
+ return _textureReferences;
+ }
+
+ public Map<String, Texture> getTextures() {
+ return _textures;
+ }
+
+ public Map<String, String> getTextureFileNames() {
+ return _textureFileNames;
+ }
+
+ public void setUseTransparency(final boolean useTransparency) {
+ _useTransparency = useTransparency;
+ }
+
+ public boolean isUseTransparency() {
+ return _useTransparency;
+ }
+
+ public void setMaterialState(final MaterialState state) {
+ _materialState = state;
+ }
+
+ public MaterialState getMaterialState() {
+ return _materialState;
+ }
+
+ public float getTransparency() {
+ return _transparency;
+ }
+
+ public void setTransparency(final float transparency) {
+ _transparency = transparency;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MeshVertPairs.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MeshVertPairs.java
new file mode 100644
index 0000000..95e1de0
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/MeshVertPairs.java
@@ -0,0 +1,43 @@
+/**
+ * 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.data;
+
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * A data object matching an Ardor3D Mesh object to the vertex indices it uses from the original <mesh><vertices>
+ * Collada data. This allows us to match weights, joint and such that are indexed to the original vertex positions to
+ * their final positions in the Mesh.
+ */
+public class MeshVertPairs {
+ /**
+ * The referenced Ardor3D Mesh.
+ */
+ private final Mesh _mesh;
+
+ /**
+ * The Collada indices. This array should be as big as the vertex count of the Mesh.
+ */
+ private final int[] _indices;
+
+ public MeshVertPairs(final Mesh mesh, final int[] indices) {
+ _mesh = mesh;
+ _indices = indices;
+ }
+
+ public Mesh getMesh() {
+ return _mesh;
+ }
+
+ public int[] getIndices() {
+ return _indices;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/NodeType.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/NodeType.java
new file mode 100644
index 0000000..93b8224
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/NodeType.java
@@ -0,0 +1,18 @@
+/**
+ * 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.data;
+
+/**
+ * The 'type' attribute of a node element.
+ */
+public enum NodeType {
+ NODE, JOINT
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SamplerTypes.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SamplerTypes.java
new file mode 100644
index 0000000..cd9c9cf
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SamplerTypes.java
@@ -0,0 +1,80 @@
+/**
+ * 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.data;
+
+import com.ardor3d.image.Texture.MagnificationFilter;
+import com.ardor3d.image.Texture.MinificationFilter;
+import com.ardor3d.image.Texture.WrapMode;
+
+/**
+ * Various enum types to be used when parsing <sampler> Collada tags.
+ */
+public abstract class SamplerTypes {
+
+ /**
+ * Enum matching Collada's texture wrapping modes to Ardor3D's.
+ */
+ public enum WrapModeType {
+ WRAP(WrapMode.Repeat), MIRROR(WrapMode.MirroredRepeat), CLAMP(WrapMode.EdgeClamp), BORDER(WrapMode.BorderClamp), NONE(
+ WrapMode.BorderClamp);
+
+ final WrapMode _wm;
+
+ private WrapModeType(final WrapMode ardorWrapMode) {
+ _wm = ardorWrapMode;
+ }
+
+ public WrapMode getArdor3dWrapMode() {
+ return _wm;
+ }
+ }
+
+ /**
+ * Enum matching Collada's texture minification modes to Ardor3D's.
+ */
+ public enum MinFilterType {
+ NONE(MinificationFilter.NearestNeighborNoMipMaps), NEAREST(MinificationFilter.NearestNeighborNoMipMaps), LINEAR(
+ MinificationFilter.BilinearNoMipMaps), NEAREST_MIPMAP_NEAREST(
+ MinificationFilter.NearestNeighborNearestMipMap), LINEAR_MIPMAP_NEAREST(
+ MinificationFilter.BilinearNearestMipMap), NEAREST_MIPMAP_LINEAR(
+ MinificationFilter.NearestNeighborLinearMipMap), LINEAR_MIPMAP_LINEAR(MinificationFilter.Trilinear);
+
+ final MinificationFilter _mf;
+
+ private MinFilterType(final MinificationFilter ardorFilter) {
+ _mf = ardorFilter;
+ }
+
+ public MinificationFilter getArdor3dFilter() {
+ return _mf;
+ }
+ }
+
+ /**
+ * Enum matching Collada's texture magnification modes to Ardor3D's.
+ */
+ public enum MagFilterType {
+ NONE(MagnificationFilter.NearestNeighbor), NEAREST(MagnificationFilter.NearestNeighbor), LINEAR(
+ MagnificationFilter.Bilinear), NEAREST_MIPMAP_NEAREST(MagnificationFilter.NearestNeighbor), LINEAR_MIPMAP_NEAREST(
+ MagnificationFilter.Bilinear), NEAREST_MIPMAP_LINEAR(MagnificationFilter.NearestNeighbor), LINEAR_MIPMAP_LINEAR(
+ MagnificationFilter.Bilinear);
+
+ final MagnificationFilter _mf;
+
+ private MagFilterType(final MagnificationFilter ardorFilter) {
+ _mf = ardorFilter;
+ }
+
+ public MagnificationFilter getArdor3dFilter() {
+ return _mf;
+ }
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SkinData.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SkinData.java
new file mode 100644
index 0000000..7fea027
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/SkinData.java
@@ -0,0 +1,126 @@
+/**
+ * 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.data;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.extension.animation.skeletal.SkeletonPose;
+import com.ardor3d.extension.animation.skeletal.SkinnedMesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.google.common.collect.Lists;
+
+/**
+ * Data class used to hold references to useful objects created during parsing of a single Collada <skin> tag.
+ */
+@SavableFactory(factoryMethod = "initSavable")
+public class SkinData implements Savable {
+
+ private SkeletonPose _pose;
+ private Node _skinBaseNode;
+ private final List<SkinnedMesh> _skins = Lists.newArrayList();
+ private final String _name;
+
+ /**
+ * Construct a new SkinData object.
+ *
+ * @param name
+ * The name for our skin data store. Should be in the format <i>[controller name][ : ][instance
+ * controller name]</i>. The names of each element should be first the name attribute, if present, or
+ * second their sid/id, or blank if neither are specified. The " : " should be left out if one or both
+ * names are blank. If both names are blank, empty string should be used.
+ */
+ public SkinData(final String name) {
+ _name = name;
+ }
+
+ public void setPose(final SkeletonPose pose) {
+ _pose = pose;
+ }
+
+ /**
+ * @return the skeletal pose created for this instance of <skin>. If there are multiple skinned meshes parsed as
+ * part of this skin, they will all share this same pose.
+ */
+ public SkeletonPose getPose() {
+ return _pose;
+ }
+
+ public void setSkinBaseNode(final Node skinBaseNode) {
+ _skinBaseNode = skinBaseNode;
+ }
+
+ /**
+ * @return the Node created to hold all SkinnedMesh objects created when parsing this skin.
+ */
+ public Node getSkinBaseNode() {
+ return _skinBaseNode;
+ }
+
+ public List<SkinnedMesh> getSkins() {
+ return _skins;
+ }
+
+ /**
+ * @return name
+ * @see #SkinData(String)
+ */
+ public String getName() {
+ return _name;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ @Override
+ public Class<? extends SkinData> getClassTag() {
+ return this.getClass();
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ final String name = capsule.readString("name", "");
+ try {
+ final Field field1 = SkinData.class.getDeclaredField("_name");
+ field1.setAccessible(true);
+ field1.set(this, name);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ _skinBaseNode = (Node) capsule.readSavable("baseNode", null);
+ _skins.clear();
+ _skins.addAll(capsule.readSavableList("skins", new ArrayList<SkinnedMesh>()));
+ _pose = (SkeletonPose) capsule.readSavable("pose", null);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_name, "name", "");
+ capsule.write(_skinBaseNode, "baseNode", null);
+ capsule.writeSavableList(_skins, "skins", null);
+ capsule.write(_pose, "pose", null);
+ }
+
+ public static SkinData initSavable() {
+ return new SkinData();
+ }
+
+ private SkinData() {
+ this(null);
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/TransformElement.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/TransformElement.java
new file mode 100644
index 0000000..85dbfb5
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/data/TransformElement.java
@@ -0,0 +1,41 @@
+/**
+ * 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.data;
+
+import java.util.Arrays;
+
+public class TransformElement {
+ private final double[] _array;
+ private final TransformElementType _type;
+
+ public enum TransformElementType {
+ Translation, Rotation, Scale, Matrix, Lookat
+ }
+
+ public TransformElement(final double[] array, final TransformElementType type) {
+ super();
+ _array = array;
+ _type = type;
+ }
+
+ @Override
+ public String toString() {
+ return "TransformElement [array=" + Arrays.toString(_array) + ", type=" + _type + "]";
+ }
+
+ public double[] getArray() {
+ return _array;
+ }
+
+ public TransformElementType getType() {
+ return _type;
+ }
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/ColladaExtraPlugin.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/ColladaExtraPlugin.java
new file mode 100644
index 0000000..49ce7bc
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/ColladaExtraPlugin.java
@@ -0,0 +1,19 @@
+/**
+ * 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.plugin;
+
+import org.jdom2.Element;
+
+public interface ColladaExtraPlugin {
+
+ boolean processExtra(Element extra, Object[] params);
+
+}
diff --git a/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/GoogleEarthPlugin.java b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/GoogleEarthPlugin.java
new file mode 100644
index 0000000..91ec7a8
--- /dev/null
+++ b/ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/plugin/GoogleEarthPlugin.java
@@ -0,0 +1,44 @@
+/**
+ * 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.plugin;
+
+import org.jdom2.Attribute;
+import org.jdom2.Element;
+
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.scenegraph.Mesh;
+
+public class GoogleEarthPlugin implements ColladaExtraPlugin {
+
+ @Override
+ public boolean processExtra(final Element extra, final Object[] params) {
+ if (params.length > 0 && params[0] instanceof Mesh) {
+ final Mesh mesh = (Mesh) params[0];
+ // should have a child: <technique profile="GOOGLEEARTH">
+ final Element technique = extra.getChild("technique");
+ if (technique != null) {
+ final Attribute profile = technique.getAttribute("profile");
+ if (profile != null && "GOOGLEEARTH".equalsIgnoreCase(profile.getValue())) {
+ for (final Element child : technique.getChildren()) {
+ // disable back face culling if it's been enabled.
+ if ("double_sided".equalsIgnoreCase(child.getName()) && "1".equals(child.getTextTrim())) {
+ final CullState cs = new CullState();
+ cs.setEnabled(false);
+ mesh.setRenderState(cs);
+ }
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}