/*******************************************************************************
* 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.IOException;
import java.io.InputStream;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Mesh.VertexDataType;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.g3d.ModelLoaderHints;
import com.badlogic.gdx.graphics.g3d.loaders.KeyframedModelLoader;
import com.badlogic.gdx.graphics.g3d.loaders.SkeletonModelLoader;
import com.badlogic.gdx.graphics.g3d.loaders.StillModelLoader;
import com.badlogic.gdx.graphics.g3d.loaders.g3d.chunks.ChunkReader;
import com.badlogic.gdx.graphics.g3d.loaders.g3d.chunks.ChunkReader.Chunk;
import com.badlogic.gdx.graphics.g3d.materials.Material;
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.skeleton.Skeleton;
import com.badlogic.gdx.graphics.g3d.model.skeleton.SkeletonAnimation;
import com.badlogic.gdx.graphics.g3d.model.skeleton.SkeletonJoint;
import com.badlogic.gdx.graphics.g3d.model.skeleton.SkeletonKeyframe;
import com.badlogic.gdx.graphics.g3d.model.skeleton.SkeletonModel;
import com.badlogic.gdx.graphics.g3d.model.skeleton.SkeletonSubMesh;
import com.badlogic.gdx.graphics.g3d.model.still.StillModel;
import com.badlogic.gdx.graphics.g3d.model.still.StillSubMesh;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.ObjectMap;
public class G3dLoader {
public static StillModel loadStillModel(FileHandle handle) {
Chunk root = null;
InputStream in = null;
try {
in = handle.read();
root = ChunkReader.readChunks(in);
// check root tag
if (root.getId() != G3dConstants.G3D_ROOT)
throw new GdxRuntimeException("Invalid root tag id: " + root.getId());
// check version
Chunk version = root.getChild(G3dConstants.VERSION_INFO);
if (version == null)
throw new GdxRuntimeException("No version chunk found");
int major = version.readByte();
int minor = version.readByte();
if (major != 0 || minor != 1)
throw new GdxRuntimeException("Invalid version, required 0.1, got " + major + "." + minor);
// read stillmodel
Chunk stillModel = root.getChild(G3dConstants.STILL_MODEL);
if (stillModel == null)
throw new GdxRuntimeException("No stillmodel chunk found");
int numSubMeshes = stillModel.readInt();
// read submeshes
StillSubMesh[] meshes = new StillSubMesh[numSubMeshes];
Chunk[] meshChunks = stillModel.getChildren(G3dConstants.STILL_SUBMESH);
if (meshChunks.length != numSubMeshes)
throw new GdxRuntimeException(
"Number of submeshes not equal to number specified in still model chunk, expected "
+ numSubMeshes + ", got " + meshChunks.length);
for (int i = 0; i < numSubMeshes; i++) {
// read submesh name and primitive type
Chunk subMesh = meshChunks[i];
String name = subMesh.readString();
int primitiveType = subMesh.readInt();
// read attributes
Chunk attributes = subMesh.getChild(G3dConstants.VERTEX_ATTRIBUTES);
if (attributes == null)
throw new GdxRuntimeException("No vertex attribute chunk given");
int numAttributes = attributes.readInt();
Chunk[] attributeChunks = attributes.getChildren(G3dConstants.VERTEX_ATTRIBUTE);
if (attributeChunks.length != numAttributes)
new GdxRuntimeException(
"Number of attributes not equal to number specified in attributes chunk, expected "
+ numAttributes + ", got " + attributeChunks.length);
VertexAttribute[] vertAttribs = new VertexAttribute[numAttributes];
for (int j = 0; j < numAttributes; j++) {
vertAttribs[j] = new VertexAttribute(attributeChunks[j].readInt(), attributeChunks[j].readInt(),
attributeChunks[j].readString());
}
// read vertices
Chunk vertices = subMesh.getChild(G3dConstants.VERTEX_LIST);
int numVertices = vertices.readInt();
float[] vertexData = vertices.readFloats();
// read indices
Chunk indices = subMesh.getChild(G3dConstants.INDEX_LIST);
int numIndices = indices.readInt();
short[] indexData = indices.readShorts();
StillSubMesh mesh = new StillSubMesh(name, new Mesh(true, numVertices, numIndices, vertAttribs),
primitiveType);
mesh.mesh.setVertices(vertexData);
mesh.mesh.setIndices(indexData);
mesh.material = new Material("default");
meshes[i] = mesh;
}
StillModel model = new StillModel(meshes);
model.setMaterial(new Material("default"));
return model;
} catch (IOException e) {
throw new GdxRuntimeException("Couldn't load still model from '" + handle.name() + "', " + e.getMessage(),
e);
} finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
}
}
}
public static KeyframedModel loadKeyframedModel(FileHandle handle) {
Chunk root = null;
InputStream in = null;
try {
in = handle.read();
root = ChunkReader.readChunks(in);
// check root tag
if (root.getId() != G3dConstants.G3D_ROOT)
throw new GdxRuntimeException("Invalid root tag id: " + root.getId());
// check version
Chunk version = root.getChild(G3dConstants.VERSION_INFO);
if (version == null)
throw new GdxRuntimeException("No version chunk found");
int major = version.readByte();
int minor = version.readByte();
if (major != 0 || minor != 1)
throw new GdxRuntimeException("Invalid version, required 0.1, got " + major + "." + minor);
// read keyframed model
Chunk stillModel = root.getChild(G3dConstants.KEYFRAMED_MODEL);
if (stillModel == null)
throw new GdxRuntimeException("No stillmodel chunk found");
int numSubMeshes = stillModel.readInt();
// read submeshes
KeyframedSubMesh[] meshes = new KeyframedSubMesh[numSubMeshes];
Chunk[] meshChunks = stillModel.getChildren(G3dConstants.KEYFRAMED_SUBMESH);
if (meshChunks.length != numSubMeshes)
throw new GdxRuntimeException(
"Number of submeshes not equal to number specified in still model chunk, expected "
+ numSubMeshes + ", got " + meshChunks.length);
for (int i = 0; i < numSubMeshes; i++) {
// read submesh name and primitive type
Chunk subMesh = meshChunks[i];
String meshName = subMesh.readString();
int primitiveType = subMesh.readInt();
int animatedComponents = subMesh.readInt();
int numAnimations = subMesh.readInt();
// read attributes
Chunk attributes = subMesh.getChild(G3dConstants.VERTEX_ATTRIBUTES);
if (attributes == null)
throw new GdxRuntimeException("No vertex attribute chunk given");
int numAttributes = attributes.readInt();
Chunk[] attributeChunks = attributes.getChildren(G3dConstants.VERTEX_ATTRIBUTE);
if (attributeChunks.length != numAttributes)
new GdxRuntimeException(
"Number of attributes not equal to number specified in attributes chunk, expected "
+ numAttributes + ", got " + attributeChunks.length);
VertexAttribute[] vertAttribs = new VertexAttribute[numAttributes];
for (int j = 0; j < numAttributes; j++) {
vertAttribs[j] = new VertexAttribute(attributeChunks[j].readInt(), attributeChunks[j].readInt(),
attributeChunks[j].readString());
}
// read static components, sort of like a bind pose mesh
Chunk vertices = subMesh.getChild(G3dConstants.VERTEX_LIST);
int numVertices = vertices.readInt();
float[] vertexData = vertices.readFloats();
// read indices
Chunk indices = subMesh.getChild(G3dConstants.INDEX_LIST);
int numIndices = indices.readInt();
short[] indexData = indices.readShorts();
// read animations
ObjectMap<String, KeyframedAnimation> animations = new ObjectMap<String, KeyframedAnimation>();
Chunk[] animationChunks = subMesh.getChildren(G3dConstants.KEYFRAMED_ANIMATION);
if (numAnimations != animationChunks.length)
throw new GdxRuntimeException(
"number of keyframed animations not equal to number specified in keyframed submesh chunk, was "
+ animationChunks.length + ", expected " + numAnimations);
for (int j = 0; j < numAnimations; j++) {
Chunk animationChunk = animationChunks[j];
String animationName = animationChunk.readString();
float frameDuration = animationChunk.readFloat();
// read keyframes
int numKeyframes = animationChunk.readInt();
Keyframe[] keyframes = new Keyframe[numKeyframes];
Chunk[] keyframeChunks = animationChunk.getChildren(G3dConstants.KEYFRAMED_FRAME);
if (numKeyframes != keyframeChunks.length)
throw new GdxRuntimeException(
"number of keyframes not equal to number specified in keyframed animation, was "
+ keyframeChunks.length + ", expected " + numKeyframes);
for (int k = 0; k < numKeyframes; k++) {
Chunk keyframeChunk = keyframeChunks[k];
float timeStamp = keyframeChunk.readFloat();
float[] keyframeVertices = keyframeChunk.readFloats();
keyframes[k] = new Keyframe(timeStamp, keyframeVertices);
}
animations.put(animationName, new KeyframedAnimation(animationName, frameDuration, keyframes));
}
Mesh mesh = new Mesh(VertexDataType.VertexArray, false, numVertices, numIndices, vertAttribs);
meshes[i] = new KeyframedSubMesh(meshName, mesh, vertexData, animations, animatedComponents,
primitiveType);
mesh.setVertices(vertexData);
mesh.setIndices(indexData);
}
KeyframedModel model = new KeyframedModel(meshes);
model.setMaterial(new Material("default"));
return model;
} catch (IOException e) {
throw new GdxRuntimeException("Couldn't load still model from '" + handle.name() + "', " + e.getMessage(),
e);
} finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
}
}
}
public static SkeletonModel loadSkeletonModel(FileHandle handle) {
Chunk root = null;
InputStream in = null;
try {
in = handle.read();
root = ChunkReader.readChunks(in);
// check root tag
if (root.getId() != G3dConstants.G3D_ROOT)
throw new GdxRuntimeException("Invalid root tag id: " + root.getId());
// check version
Chunk version = root.getChild(G3dConstants.VERSION_INFO);
if (version == null)
throw new GdxRuntimeException("No version chunk found");
int major = version.readByte();
int minor = version.readByte();
if (major != 0 || minor != 1)
throw new GdxRuntimeException("Invalid version, required 0.1, got " + major + "." + minor);
// read skeleton model
Chunk skeletonModel = root.getChild(G3dConstants.SKELETON_MODEL);
if (skeletonModel == null)
throw new GdxRuntimeException("No skeletonModel chunk found");
int numSubMeshes = skeletonModel.readInt();
// read submeshes
SkeletonSubMesh[] meshes = new SkeletonSubMesh[numSubMeshes];
Chunk[] meshChunks = skeletonModel.getChildren(G3dConstants.SKELETON_SUBMESH);
if (meshChunks.length != numSubMeshes)
throw new GdxRuntimeException(
"Number of submeshes not equal to number specified in still model chunk, expected "
+ numSubMeshes + ", got " + meshChunks.length);
for (int i = 0; i < numSubMeshes; i++) {
Chunk subMeshChunk = meshChunks[i];
// read attributes
Chunk attributes = subMeshChunk.getChild(G3dConstants.VERTEX_ATTRIBUTES);
if (attributes == null)
throw new GdxRuntimeException("No vertex attribute chunk given");
int numAttributes = attributes.readInt();
Chunk[] attributeChunks = attributes.getChildren(G3dConstants.VERTEX_ATTRIBUTE);
if (attributeChunks.length != numAttributes)
new GdxRuntimeException(
"Number of attributes not equal to number specified in attributes chunk, expected "
+ numAttributes + ", got " + attributeChunks.length);
VertexAttribute[] vertAttribs = new VertexAttribute[numAttributes];
for (int j = 0; j < numAttributes; j++) {
vertAttribs[j] = new VertexAttribute(attributeChunks[j].readInt(), attributeChunks[j].readInt(),
attributeChunks[j].readString());
}
// read static components, sort of like a bind pose mesh
Chunk vertices = subMeshChunk.getChild(G3dConstants.VERTEX_LIST);
int numVertices = vertices.readInt();
float[] meshVertices = vertices.readFloats();
// read indices
Chunk indices = subMeshChunk.getChild(G3dConstants.INDEX_LIST);
int numIndices = indices.readInt();
short[] meshIndices = indices.readShorts();
//read bone weight
Chunk boneWeights = subMeshChunk.getChild(G3dConstants.BONE_WEIGHTS);
int numBonesWeights = boneWeights.readInt();
Chunk[] boneWeightChunks = boneWeights.getChildren(G3dConstants.BONE_WEIGHT);
if (attributeChunks.length != numAttributes)
new GdxRuntimeException(
"Number of bone weights not equal to number specified in bone weights chunk, expected "
+ numBonesWeights + ", got " + boneWeightChunks.length);
float[][] meshBoneWeights = new float[numBonesWeights][];
for (int j = 0; j < numBonesWeights; j++) {
int count = boneWeightChunks[j].readInt();
meshBoneWeights[j] = boneWeightChunks[j].readFloats();
}
//read bone assignment
Chunk boneAssignments = subMeshChunk.getChild(G3dConstants.BONE_ASSIGNMENTS);
int numBoneAssignments = boneAssignments.readInt();
Chunk[] boneAssignmentChunks = boneAssignments.getChildren(G3dConstants.BONE_ASSIGNMENT);
if (boneAssignmentChunks.length != numBoneAssignments)
new GdxRuntimeException(
"Number of bone assignment not equal to number specified in bone assignment chunk, expected "
+ numBoneAssignments + ", got " + boneAssignmentChunks.length);
int[][] meshBoneAssignments = new int[numBoneAssignments][];
for (int j = 0; j < numBoneAssignments; j++) {
int count = boneAssignmentChunks[j].readInt();
meshBoneAssignments[j] = boneAssignmentChunks[j].readInts();
}
SkeletonSubMesh subMesh = new SkeletonSubMesh(subMeshChunk.readString(), new Mesh(false, numVertices,
numIndices, vertAttribs), subMeshChunk.readInt());
subMesh.indices = meshIndices;
subMesh.boneAssignments = meshBoneAssignments;
subMesh.boneWeights = meshBoneWeights;
subMesh.vertices = meshVertices;
subMesh.mesh.setVertices(subMesh.vertices);
subMesh.mesh.setIndices(subMesh.indices);
subMesh.skinnedVertices = new float[subMesh.vertices.length];
System.arraycopy(subMesh.vertices, 0, subMesh.skinnedVertices, 0, subMesh.vertices.length);
meshes[i] = subMesh;
}
//read Skeleton hierarchy
Skeleton skeleton = new Skeleton();
Chunk skeletonChunk = skeletonModel.getChild(G3dConstants.SKELETON);
{
// read Skeleton hierarchy
Chunk hierarchy = skeletonChunk.getChild(G3dConstants.SKELETON_HIERARCHY);
int numHierarchyJoints = hierarchy.readInt();
for (int i = 0; i < numHierarchyJoints; i++) {
skeleton.hierarchy.add(readSkeletonJoint(hierarchy));
}
// read Skeleton animations
Chunk animations = skeletonChunk.getChild(G3dConstants.SKELETON_ANIMATIONS);
int numAnimations = animations.readInt();
Chunk[] animationChunks = animations.getChildren(G3dConstants.SKELETON_ANIMATION);
if (animationChunks.length != numAnimations)
new GdxRuntimeException(
"Number of animations not equal to number specified in animations chunk, expected "
+ numAnimations + ", got " + animationChunks.length);
for (int i = 0; i < numAnimations; i++) {
Chunk animation = animationChunks[i];
String name = animation.readString();
float totalDuration = animation.readFloat();
int numJoints = animation.readInt();
SkeletonKeyframe perJointKeyFrames[][] = new SkeletonKeyframe[numJoints][];
for (int j = 0; j < numJoints; j++) {
int numFrames = animation.readInt();
perJointKeyFrames[j] = new SkeletonKeyframe[numFrames];
for (int k = 0; k < numFrames; k++) {
SkeletonKeyframe frame = new SkeletonKeyframe();
frame.timeStamp = animation.readFloat();
frame.parentIndex = animation.readInt();
frame.position.x = animation.readFloat();
frame.position.y = animation.readFloat();
frame.position.z = animation.readFloat();
frame.rotation.w = animation.readFloat();
frame.rotation.x = animation.readFloat();
frame.rotation.y = animation.readFloat();
frame.rotation.z = animation.readFloat();
frame.scale.x = animation.readFloat();
frame.scale.y = animation.readFloat();
frame.scale.z = animation.readFloat();
perJointKeyFrames[j][k] = frame;
}
}
skeleton.animations.put(name, new SkeletonAnimation(name, totalDuration, perJointKeyFrames));
}
}
skeleton.buildFromHierarchy();
SkeletonModel model = new SkeletonModel(skeleton, meshes);
model.setMaterial(new Material("default"));
return model;
} catch (IOException e) {
throw new GdxRuntimeException("Couldn't load skeleton model from '" + handle.name() + "', "
+ e.getMessage(), e);
} finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
}
}
}
private static SkeletonJoint readSkeletonJoint(Chunk jointChunk) {
SkeletonJoint joint = new SkeletonJoint();
joint.name = jointChunk.readString();
joint.position.x = jointChunk.readFloat();
joint.position.y = jointChunk.readFloat();
joint.position.z = jointChunk.readFloat();
joint.rotation.w = jointChunk.readFloat();
joint.rotation.x = jointChunk.readFloat();
joint.rotation.y = jointChunk.readFloat();
joint.rotation.z = jointChunk.readFloat();
joint.scale.x = jointChunk.readFloat();
joint.scale.y = jointChunk.readFloat();
joint.scale.z = jointChunk.readFloat();
int count = jointChunk.readInt();
for (int i = 0; i < count; i++) {
SkeletonJoint child = readSkeletonJoint(jointChunk);
child.parent = joint;
joint.children.add(child);
}
return joint;
}
public static class G3dStillModelLoader implements StillModelLoader {
@Override
public StillModel load(FileHandle handle, ModelLoaderHints hints) {
return G3dLoader.loadStillModel(handle);
}
}
public static class G3dKeyframedModelLoader implements KeyframedModelLoader {
@Override
public KeyframedModel load(FileHandle handle, ModelLoaderHints hints) {
return G3dLoader.loadKeyframedModel(handle);
}
}
public static class G3dSkeletonModelLoader implements SkeletonModelLoader {
@Override
public SkeletonModel load(FileHandle handle, ModelLoaderHints hints) {
return G3dLoader.loadSkeletonModel(handle);
}
}
}