/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g3d.loaders.collada; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.Mesh; import com.badlogic.gdx.graphics.VertexAttribute; import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.g3d.model.skeleton.SkeletonSubMesh; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.XmlReader.Element; public class Faces { protected static final String VERTEX = "VERTEX"; protected static final String TEXCOORD = "TEXCOORD"; protected static final String NORMAL = "NORMAL"; protected static final String TANGENT = "TEXTANGENT"; protected static final String BITANGENT = "TEXBINORMAL"; protected static final String COLOR = "COLOR"; int count = 0; final Map<String, Source> sourcesMap; final Map<String, String> mappings; Array<Element> inputs; Source[] sources; Array<VertexIndices> triangles; Array<VertexIndices> vertices; int numVertices = 0; int numIndices = 0; int primitiveType = GL10.GL_TRIANGLES; public Faces (Element faces, Map<String, String> mappings, Map<String, Source> sources) { this.sourcesMap = sources; this.mappings = mappings; parseVertices(faces); triangulate(faces); this.numIndices = triangles.size; } /** Conditions the inputs for a triangles/polylist list of faces and generates unique vertices while merging duplicate vertices. * @param faces */ private void parseVertices (Element faces) { inputs = faces.getChildrenByName("input"); if (inputs == null) throw new GdxRuntimeException("no <input> elements in <triangles>/<polylist>"); int[] offsets = new int[inputs.size]; int stride = 0; // normalize source references, should use the URI/address scheme of Collada FIXME // calculate stride and prepare to untangle the index lists mess... sources = new Source[inputs.size]; for (int i = 0; i < inputs.size; i++) { Element input = inputs.get(i); // map source if it was defined in <vertices> tag String source = input.getAttribute("source").substring(1); if (mappings.containsKey(source)) { input.setAttribute("source", mappings.get(source)); } else { input.setAttribute("source", source); } // check whether source exists source = input.getAttribute("source"); if (!sourcesMap.containsKey(source)) throw new GdxRuntimeException("source '" + source + "' not in mesh> but in <triangle>"); sources[i] = sourcesMap.get(source); offsets[i] = Integer.parseInt(input.getAttribute("offset")); stride = Math.max(offsets[i], stride); } // addjust for zero source offsets stride += 1; // parse <p> indices, yeah, that takes up a bit more memory. String[] tokens = faces.getChildByName("p").getText().split("\\s+"); int[] indices = new int[tokens.length]; for (int i = 0; i < tokens.length; i++) { indices[i] = Integer.parseInt(tokens[i]); } // untangle indices on a per source basis Map<VertexIndices, VertexIndices> indicesSet = new HashMap<VertexIndices, VertexIndices>(); VertexIndices vertex = new VertexIndices(inputs.size); triangles = new Array<VertexIndices>(indices.length / stride); vertices = new Array<VertexIndices>(indices.length / stride); int index = 0; for (int i = 0; i < indices.length; i += stride) { for (int j = 0; j < inputs.size; j++) { vertex.indices[j] = indices[i + offsets[j]]; vertex.index = index; } VertexIndices lookup = indicesSet.get(vertex); if (lookup != null) { triangles.add(lookup); } else { triangles.add(vertex); vertices.add(vertex); indicesSet.put(vertex, vertex); vertex = new VertexIndices(inputs.size); index++; } } numVertices = index; } /** This method triangulates the faces if they are given as a polylist. Does nothing in case the faces are given as triangles * already. * * @param polyList */ private void triangulate (Element polyList) { if (!polyList.getName().equals("polylist")) return; Element colladaPolys = polyList.getChildByName("vcount"); if (colladaPolys == null) throw new GdxRuntimeException("<polylist> does not contain <vcount> element"); String[] tokens = colladaPolys.getText().split("\\s+"); int[] polys = new int[tokens.length]; int vertexCount = 0; for (int i = 0; i < tokens.length; i++) { int verts = Integer.parseInt(tokens[i]); polys[i] = verts; vertexCount += verts; } Array<VertexIndices> newVertices = new Array<VertexIndices>(vertexCount); int idx = 0; for (int i = 0; i < polys.length; i++) { int numVertices = polys[i]; VertexIndices baseVertex = triangles.get(idx++); for (int j = 1; j < numVertices - 1; j++) { newVertices.add(baseVertex); newVertices.add(triangles.get(idx)); newVertices.add(triangles.get(idx + 1)); idx++; } idx++; } triangles = newVertices; } public Mesh getMesh () { float[] verts = new float[getVertexSize() * numVertices]; short[] indices = new short[numIndices]; VertexAttribute[] attributes = getVertexAttributes(); for (int i = 0; i < numIndices; i++) { VertexIndices vertex = triangles.get(i); if (vertex.index > Short.MAX_VALUE || vertex.index < Short.MIN_VALUE) throw new GdxRuntimeException("index to big for short: " + vertex.index); indices[i] = (short)vertex.index; } //int idx = 0; int destOffset = 0; for (int i = 0; i < vertices.size; i++) { VertexIndices vertex = vertices.get(i); for (int j = 0; j < sources.length; j++) { Source source = sources[j]; float[] data = source.data; int index = vertex.indices[j]; int components = source.components; int sourceOffset = index * components; for (int k = 0; k < components; k++) { if ((attributes[j].usage == Usage.TextureCoordinates) && k == 1) { verts[destOffset++] = 1 - data[sourceOffset++]; } else { verts[destOffset++] = data[sourceOffset++]; } } } } Mesh mesh = new Mesh(true, vertices.size, indices.length, attributes); mesh.setVertices(verts); mesh.setIndices(indices); return mesh; } public SkeletonSubMesh getSkeletonSubMesh (Skin skin) { float[] verts = new float[getVertexSize() * numVertices]; short[] indices = new short[numIndices]; VertexAttribute[] attributes = getVertexAttributes(); for (int i = 0; i < numIndices; i++) { VertexIndices vertex = triangles.get(i); if (vertex.index > Short.MAX_VALUE || vertex.index < Short.MIN_VALUE) throw new GdxRuntimeException("index to big for short: " + vertex.index); indices[i] = (short)vertex.index; } //int idx = 0; int destOffset = 0; for (int i = 0; i < vertices.size; i++) { VertexIndices vertex = vertices.get(i); for (int j = 0; j < sources.length; j++) { Source source = sources[j]; float[] data = source.data; int index = vertex.indices[j]; int components = source.components; int sourceOffset = index * components; for (int k = 0; k < components; k++) { if ((attributes[j].usage == Usage.TextureCoordinates) && k == 1) { verts[destOffset++] = 1 - data[sourceOffset++]; } else { verts[destOffset++] = data[sourceOffset++]; } } } } Mesh mesh = new Mesh(false, vertices.size, indices.length, attributes); mesh.setVertices(verts); mesh.setIndices(indices); SkeletonSubMesh submesh = new SkeletonSubMesh("", mesh, GL10.GL_TRIANGLES); submesh.vertices = verts; submesh.indices = indices; submesh.skinnedVertices = new float[submesh.vertices.length]; System.arraycopy(submesh.vertices, 0, submesh.skinnedVertices, 0, submesh.vertices.length); submesh.boneAssignments = new int[vertices.size][]; submesh.boneWeights = new float[vertices.size][]; for(int i=0;i<skin.boneIndex.length;i++){ for(int j=0;j<vertices.size;j++) { //TODO: use lookup for position instead of assuming it is in the first spot if(vertices.get(j).indices[0] ==i){ submesh.boneAssignments[j] = skin.boneIndex[i]; submesh.boneWeights[j] = skin.boneWeight[i]; } } } return submesh; } private VertexAttribute[] getVertexAttributes () { VertexAttribute[] attributes = new VertexAttribute[inputs.size]; int texUnit = 0; for (int i = 0; i < inputs.size; i++) { Element input = inputs.get(i); String semantic = input.getAttribute("semantic"); Source source = sourcesMap.get(input.getAttribute("source")); int usage = getVertexAttributeUsage(semantic); int components = source.components; String alias = getVertexAttributeAlias(semantic); if (alias.equals(ShaderProgram.TEXCOORD_ATTRIBUTE)) alias += texUnit++; attributes[i] = new VertexAttribute(usage, components, alias); } return attributes; } private int getVertexSize () { int size = 0; for (int i = 0; i < inputs.size; i++) { size += sourcesMap.get(inputs.get(i).getAttribute("source")).components; } return size; } private int getVertexAttributeUsage (String attribute) { if (attribute.equals(VERTEX)) return Usage.Position; if (attribute.equals(TEXCOORD)) return Usage.TextureCoordinates; if (attribute.equals(NORMAL)) return Usage.Normal; return Usage.Generic; } private String getVertexAttributeAlias (String attribute) { if (attribute.equals(VERTEX)) return ShaderProgram.POSITION_ATTRIBUTE; if (attribute.equals(TEXCOORD)) return ShaderProgram.TEXCOORD_ATTRIBUTE; if (attribute.equals(NORMAL)) return ShaderProgram.NORMAL_ATTRIBUTE; if (attribute.equals(TANGENT)) return ShaderProgram.TANGENT_ATTRIBUTE; if (attribute.equals(BITANGENT)) return ShaderProgram.BINORMAL_ATTRIBUTE; if (attribute.equals(COLOR)) return ShaderProgram.COLOR_ATTRIBUTE; throw new GdxRuntimeException("can't map semantic '" + attribute + "' to alias, must be VERTEX, TEXCOORD, NORMAL, TANGENT or BITANGENT"); } /** Helper class that stores a vertex in form of indices into sources. * * @author mzechner */ static class VertexIndices { int[] indices; int index; public VertexIndices (int size) { this.indices = new int[size]; } @Override public int hashCode () { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(indices); return result; } @Override public boolean equals (Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; VertexIndices other = (VertexIndices)obj; if (!Arrays.equals(indices, other.indices)) return false; return true; } @Override public String toString () { return index + ": " + Arrays.toString(indices); } } }