/*******************************************************************************
* 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.g3d;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Mesh.VertexDataType;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.g3d.ModelLoaderHints;
import com.badlogic.gdx.graphics.g3d.loaders.KeyframedModelLoader;
import com.badlogic.gdx.graphics.g3d.loaders.StillModelLoader;
import com.badlogic.gdx.graphics.g3d.model.keyframe.Keyframe;
import com.badlogic.gdx.graphics.g3d.model.keyframe.KeyframedAnimation;
import com.badlogic.gdx.graphics.g3d.model.keyframe.KeyframedModel;
import com.badlogic.gdx.graphics.g3d.model.keyframe.KeyframedSubMesh;
import com.badlogic.gdx.graphics.g3d.model.still.StillModel;
import com.badlogic.gdx.graphics.g3d.model.still.StillSubMesh;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.ObjectMap;
/**
* Class to import the G3D text format.
*
* @author mzechner
*/
public class G3dtLoader {
public static KeyframedModel loadKeyframedModel(FileHandle handle, boolean flipV) {
return loadKeyframedModel(handle.read(), flipV);
}
public static StillModel loadStillModel(FileHandle handle, boolean flipV) {
return loadStillModel(handle.read(), flipV);
}
static int lineNum = 0;
static String line = null;
public static StillModel loadStillModel(InputStream stream, boolean flipV) {
BufferedReader in = new BufferedReader(new InputStreamReader(stream));
lineNum = 1;
try {
String version = readString(in);
if (!version.equals("g3dt-still-1.0"))
throw new GdxRuntimeException("incorrect version");
int numMeshes = readInt(in);
StillSubMesh[] subMeshes = new StillSubMesh[numMeshes];
for (int i = 0; i < numMeshes; i++) {
subMeshes[i] = readStillSubMesh(in, flipV);
}
StillModel model = new StillModel(subMeshes);
return model;
} catch (Throwable e) {
throw new GdxRuntimeException("Couldn't read keyframed model, error in line " + lineNum + ", '" + line
+ "' : " + e.getMessage(), e);
}
}
private static StillSubMesh readStillSubMesh(BufferedReader in, boolean flipV) throws IOException {
String name = readString(in);
IntArray indices = readFaces(in);
int numVertices = readInt(in);
int numAttributes = readInt(in);
if (!readString(in).equals("position"))
throw new GdxRuntimeException("first attribute must be position.");
int numUvs = 0;
boolean hasNormals = false;
for (int i = 1; i < numAttributes; i++) {
String attributeType = readString(in);
if (!attributeType.equals("normal") && !attributeType.equals("uv"))
throw new GdxRuntimeException("attribute name must be normal or uv");
if (attributeType.equals("normal")) {
if (i != 1)
throw new GdxRuntimeException("attribute normal must be second attribute");
hasNormals = true;
}
if (attributeType.equals("uv")) {
numUvs++;
}
}
VertexAttribute[] vertexAttributes = createVertexAttributes(hasNormals, numUvs);
int vertexSize = new VertexAttributes(vertexAttributes).vertexSize / 4;
float[] vertices = new float[numVertices * vertexSize];
int idx = 0;
int uvOffset = hasNormals ? 6 : 3;
for (int i = 0; i < numVertices; i++) {
readFloatArray(in, vertices, idx);
if (flipV) {
for (int j = idx + uvOffset + 1; j < idx + uvOffset + numUvs * 2; j += 2) {
vertices[j] = 1 - vertices[j];
}
}
idx += vertexSize;
}
Mesh mesh = new Mesh(true, numVertices, indices.size, vertexAttributes);
mesh.setVertices(vertices);
mesh.setIndices(convertToShortArray(indices));
return new StillSubMesh(name, mesh, GL10.GL_TRIANGLES);
}
public static KeyframedModel loadKeyframedModel(InputStream stream, boolean flipV) {
BufferedReader in = new BufferedReader(new InputStreamReader(stream));
lineNum = 1;
try {
String version = readString(in);
if (!version.equals("g3dt-keyframed-1.0"))
throw new GdxRuntimeException("incorrect version");
int numMeshes = readInt(in);
KeyframedSubMesh[] subMeshes = new KeyframedSubMesh[numMeshes];
for (int i = 0; i < numMeshes; i++) {
subMeshes[i] = readMesh(in, flipV);
}
KeyframedModel model = new KeyframedModel(subMeshes);
model.setAnimation(model.getAnimations()[0].name, 0, false);
return model;
} catch (Throwable e) {
throw new GdxRuntimeException("Couldn't read keyframed model, error in line " + lineNum + ", '" + line
+ "' : " + e.getMessage(), e);
}
}
private static KeyframedSubMesh readMesh(BufferedReader in, boolean flipV) throws IOException {
String name = readString(in);
IntArray indices = readFaces(in);
int numVertices = readInt(in);
int numAttributes = readInt(in);
if (!readString(in).equals("position"))
throw new GdxRuntimeException("first attribute must be position.");
Array<FloatArray> uvSets = new Array<FloatArray>();
boolean hasNormals = false;
for (int i = 1; i < numAttributes; i++) {
String attributeType = readString(in);
if (!attributeType.equals("normal") && !attributeType.equals("uv"))
throw new GdxRuntimeException("attribute name must be normal or uv");
if (attributeType.equals("normal")) {
if (i != 1)
throw new GdxRuntimeException("attribute normal must be second attribute");
hasNormals = true;
}
if (attributeType.equals("uv")) {
uvSets.add(readUVSet(in, numVertices, flipV));
}
}
int animatedComponents = hasNormals ? 6 : 3;
VertexAttribute[] vertexAttributes = createVertexAttributes(hasNormals, uvSets.size);
int numAnimations = readInt(in);
ObjectMap<String, KeyframedAnimation> animations = new ObjectMap<String, KeyframedAnimation>(numAnimations);
for (int i = 0; i < numAnimations; i++) {
String animationName = readString(in);
int numKeyframes = readInt(in);
float frameDuration = readFloat(in); // in seconds
Keyframe[] keyframes = new Keyframe[numKeyframes];
float time = 0;
FloatArray vertex = new FloatArray(animatedComponents);
for (int frame = 0; frame < numKeyframes; frame++) {
float[] vertices = new float[numVertices * (animatedComponents)];
int idx = 0;
for (int j = 0; j < numVertices; j++) {
idx = readFloatArray(in, vertices, idx);
}
Keyframe keyframe = new Keyframe(time, vertices);
keyframes[frame] = keyframe;
time += frameDuration;
}
KeyframedAnimation animation = new KeyframedAnimation(animationName, frameDuration, keyframes);
animations.put(animationName, animation);
}
KeyframedSubMesh mesh = new KeyframedSubMesh(name, new Mesh(VertexDataType.VertexArray, false, numVertices,
indices.size, createVertexAttributes(hasNormals, uvSets.size)), buildVertices(numVertices, hasNormals,
uvSets), animations, animatedComponents, GL10.GL_TRIANGLES);
mesh.mesh.setIndices(convertToShortArray(indices));
mesh.mesh.setVertices(mesh.blendedVertices);
return mesh;
}
private static float[] buildVertices(int numVertices, boolean hasNormals, Array<FloatArray> uvSets) {
float[] vertices = new float[numVertices * (3 + (hasNormals ? 3 : 0) + uvSets.size * 2)];
int idx = 0;
int idxUv = 0;
for (int i = 0; i < numVertices; i++) {
vertices[idx++] = 0;
vertices[idx++] = 0;
vertices[idx++] = 0;
if (hasNormals) {
vertices[idx++] = 0;
vertices[idx++] = 0;
vertices[idx++] = 0;
}
for (int j = 0; j < uvSets.size; j++) {
vertices[idx++] = uvSets.get(j).get(idxUv);
vertices[idx++] = uvSets.get(j).get(idxUv + 1);
}
idxUv += 2;
}
return vertices;
}
private static VertexAttribute[] createVertexAttributes(boolean hasNormals, int uvs) {
VertexAttribute[] attributes = new VertexAttribute[1 + (hasNormals ? 1 : 0) + uvs];
int idx = 0;
attributes[idx++] = new VertexAttribute(Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE);
if (hasNormals)
attributes[idx++] = new VertexAttribute(Usage.Normal, 3, ShaderProgram.NORMAL_ATTRIBUTE);
for (int i = 0; i < uvs; i++) {
attributes[idx++] = new VertexAttribute(Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + i);
}
return attributes;
}
private static FloatArray readUVSet(BufferedReader in, int numVertices, boolean flipV) throws IOException {
FloatArray uvSet = new FloatArray(numVertices * 2);
FloatArray uv = new FloatArray(2);
for (int i = 0; i < numVertices; i++) {
readFloatArray(in, uv);
uvSet.add(uv.items[0]);
uvSet.add(flipV ? 1 - uv.items[1] : uv.items[1]);
}
return uvSet;
}
private static IntArray readFaces(BufferedReader in) throws NumberFormatException, IOException {
int numFaces = readInt(in);
IntArray faceIndices = new IntArray();
IntArray triangles = new IntArray();
IntArray indices = new IntArray();
for (int face = 0; face < numFaces; face++) {
readIntArray(in, faceIndices);
int numIndices = faceIndices.get(0);
triangles.clear();
int baseIndex = faceIndices.get(1);
for (int i = 2; i < numIndices; i++) {
triangles.add(baseIndex);
triangles.add(faceIndices.items[i]);
triangles.add(faceIndices.items[i + 1]);
}
indices.addAll(triangles);
}
indices.shrink();
return indices;
}
private static short[] convertToShortArray(IntArray array) {
short[] shortArray = new short[array.size];
for (int i = 0; i < array.size; i++) {
shortArray[i] = (short) array.items[i];
}
return shortArray;
}
private static float readFloat(BufferedReader in) throws NumberFormatException, IOException {
lineNum++;
return Float.parseFloat(read(in).trim());
}
private static int readInt(BufferedReader in) throws NumberFormatException, IOException {
lineNum++;
return (int) (Math.floor(Float.parseFloat(read(in).trim())));
}
private static String readString(BufferedReader in) throws IOException {
lineNum++;
return read(in);
}
private static void readFloatArray(BufferedReader in, FloatArray array) throws IOException {
lineNum++;
String[] tokens = read(in).split(",");
int len = tokens.length;
array.clear();
for (int i = 0; i < len; i++) {
array.add(Float.parseFloat(tokens[i].trim()));
}
}
private static int readFloatArray(BufferedReader in, float[] array, int idx) throws IOException {
lineNum++;
String[] tokens = read(in).split(",");
int len = tokens.length;
for (int i = 0; i < len; i++) {
array[idx++] = Float.parseFloat(tokens[i].trim());
}
return idx;
}
private static void readIntArray(BufferedReader in, IntArray array) throws IOException {
String[] tokens = read(in).split(",");
int len = tokens.length;
array.clear();
for (int i = 0; i < len; i++) {
array.add(Integer.parseInt(tokens[i].trim()));
}
}
private static String read(BufferedReader in) throws IOException {
line = in.readLine();
return line;
}
public static class G3dtStillModelLoader implements StillModelLoader {
@Override
public StillModel load(FileHandle handle, ModelLoaderHints hints) {
return G3dtLoader.loadStillModel(handle, hints.flipV);
}
}
public static class G3dtKeyframedModelLoader implements KeyframedModelLoader {
@Override
public KeyframedModel load(FileHandle handle, ModelLoaderHints hints) {
return G3dtLoader.loadKeyframedModel(handle, hints.flipV);
}
}
}