+ 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);
// attach any attachment points found for the skeleton's joints
addAttachments(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.