/*******************************************************************************
* 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.loader;
import com.badlogic.gdx.assets.loaders.FileHandleResolver;
import com.badlogic.gdx.assets.loaders.ModelLoader;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.g3d.model.data.ModelAnimation;
import com.badlogic.gdx.graphics.g3d.model.data.ModelData;
import com.badlogic.gdx.graphics.g3d.model.data.ModelMaterial;
import com.badlogic.gdx.graphics.g3d.model.data.ModelMesh;
import com.badlogic.gdx.graphics.g3d.model.data.ModelMeshPart;
import com.badlogic.gdx.graphics.g3d.model.data.ModelNode;
import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeAnimation;
import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeKeyframe;
import com.badlogic.gdx.graphics.g3d.model.data.ModelNodePart;
import com.badlogic.gdx.graphics.g3d.model.data.ModelTexture;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ArrayMap;
import com.badlogic.gdx.utils.BaseJsonReader;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.JsonValue;
public class G3dModelLoader extends ModelLoader<ModelLoader.ModelParameters> {
public static final short VERSION_HI = 0;
public static final short VERSION_LO = 1;
protected final BaseJsonReader reader;
public G3dModelLoader (final BaseJsonReader reader) {
this(reader, null);
}
public G3dModelLoader (BaseJsonReader reader, FileHandleResolver resolver) {
super(resolver);
this.reader = reader;
}
@Override
public ModelData loadModelData (FileHandle fileHandle, ModelLoader.ModelParameters parameters) {
return parseModel(fileHandle);
}
public ModelData parseModel (FileHandle handle) {
JsonValue json = reader.parse(handle);
ModelData model = new ModelData();
JsonValue version = json.require("version");
model.version[0] = version.getShort(0);
model.version[1] = version.getShort(1);
if (model.version[0] != VERSION_HI || model.version[1] != VERSION_LO)
throw new GdxRuntimeException("Model version not supported");
model.id = json.getString("id", "");
parseMeshes(model, json);
parseMaterials(model, json, handle.parent().path());
parseNodes(model, json);
parseAnimations(model, json);
return model;
}
private void parseMeshes (ModelData model, JsonValue json) {
JsonValue meshes = json.get("meshes");
if (meshes != null) {
model.meshes.ensureCapacity(meshes.size);
for (JsonValue mesh = meshes.child; mesh != null; mesh = mesh.next) {
ModelMesh jsonMesh = new ModelMesh();
String id = mesh.getString("id", "");
jsonMesh.id = id;
JsonValue attributes = mesh.require("attributes");
jsonMesh.attributes = parseAttributes(attributes);
jsonMesh.vertices = mesh.require("vertices").asFloatArray();
JsonValue meshParts = mesh.require("parts");
Array<ModelMeshPart> parts = new Array<ModelMeshPart>();
for (JsonValue meshPart = meshParts.child; meshPart != null; meshPart = meshPart.next) {
ModelMeshPart jsonPart = new ModelMeshPart();
String partId = meshPart.getString("id", null);
if (partId == null) {
throw new GdxRuntimeException("Not id given for mesh part");
}
for (ModelMeshPart other : parts) {
if (other.id.equals(partId)) {
throw new GdxRuntimeException("Mesh part with id '" + partId + "' already in defined");
}
}
jsonPart.id = partId;
String type = meshPart.getString("type", null);
if (type == null) {
throw new GdxRuntimeException("No primitive type given for mesh part '" + partId + "'");
}
jsonPart.primitiveType = parseType(type);
jsonPart.indices = meshPart.require("indices").asShortArray();
parts.add(jsonPart);
}
jsonMesh.parts = parts.toArray(ModelMeshPart.class);
model.meshes.add(jsonMesh);
}
}
}
private int parseType (String type) {
if (type.equals("TRIANGLES")) {
return GL20.GL_TRIANGLES;
} else if (type.equals("LINES")) {
return GL20.GL_LINES;
} else if (type.equals("POINTS")) {
return GL20.GL_POINTS;
} else if (type.equals("TRIANGLE_STRIP")) {
return GL20.GL_TRIANGLE_STRIP;
} else if (type.equals("LINE_STRIP")) {
return GL20.GL_LINE_STRIP;
} else {
throw new GdxRuntimeException("Unknown primitive type '" + type
+ "', should be one of triangle, trianglestrip, line, linestrip, lineloop or point");
}
}
private VertexAttribute[] parseAttributes (JsonValue attributes) {
Array<VertexAttribute> vertexAttributes = new Array<VertexAttribute>();
int unit = 0;
int blendWeightCount = 0;
for (JsonValue value = attributes.child; value != null; value = value.next) {
String attribute = value.asString();
String attr = (String)attribute;
if (attr.equals("POSITION")) {
vertexAttributes.add(VertexAttribute.Position());
} else if (attr.equals("NORMAL")) {
vertexAttributes.add(VertexAttribute.Normal());
} else if (attr.equals("COLOR")) {
vertexAttributes.add(VertexAttribute.ColorUnpacked());
} else if (attr.equals("COLORPACKED")) {
vertexAttributes.add(VertexAttribute.ColorPacked());
} else if (attr.equals("TANGENT")) {
vertexAttributes.add(VertexAttribute.Tangent());
} else if (attr.equals("BINORMAL")) {
vertexAttributes.add(VertexAttribute.Binormal());
} else if (attr.startsWith("TEXCOORD")) {
vertexAttributes.add(VertexAttribute.TexCoords(unit++));
} else if (attr.startsWith("BLENDWEIGHT")) {
vertexAttributes.add(VertexAttribute.BoneWeight(blendWeightCount++));
} else {
throw new GdxRuntimeException("Unknown vertex attribute '" + attr
+ "', should be one of position, normal, uv, tangent or binormal");
}
}
return vertexAttributes.toArray(VertexAttribute.class);
}
private void parseMaterials (ModelData model, JsonValue json, String materialDir) {
JsonValue materials = json.get("materials");
if (materials == null) {
// we should probably create some default material in this case
} else {
model.materials.ensureCapacity(materials.size);
for (JsonValue material = materials.child; material != null; material = material.next) {
ModelMaterial jsonMaterial = new ModelMaterial();
String id = material.getString("id", null);
if (id == null) throw new GdxRuntimeException("Material needs an id.");
jsonMaterial.id = id;
// Read material colors
final JsonValue diffuse = material.get("diffuse");
if (diffuse != null) jsonMaterial.diffuse = parseColor(diffuse);
final JsonValue ambient = material.get("ambient");
if (ambient != null) jsonMaterial.ambient = parseColor(ambient);
final JsonValue emissive = material.get("emissive");
if (emissive != null) jsonMaterial.emissive = parseColor(emissive);
final JsonValue specular = material.get("specular");
if (specular != null) jsonMaterial.specular = parseColor(specular);
final JsonValue reflection = material.get("reflection");
if (reflection != null) jsonMaterial.reflection = parseColor(reflection);
// Read shininess
jsonMaterial.shininess = material.getFloat("shininess", 0.0f);
// Read opacity
jsonMaterial.opacity = material.getFloat("opacity", 1.0f);
// Read textures
JsonValue textures = material.get("textures");
if (textures != null) {
for (JsonValue texture = textures.child; texture != null; texture = texture.next) {
ModelTexture jsonTexture = new ModelTexture();
String textureId = texture.getString("id", null);
if (textureId == null) throw new GdxRuntimeException("Texture has no id.");
jsonTexture.id = textureId;
String fileName = texture.getString("filename", null);
if (fileName == null) throw new GdxRuntimeException("Texture needs filename.");
jsonTexture.fileName = materialDir + (materialDir.length() == 0 || materialDir.endsWith("/") ? "" : "/")
+ fileName;
jsonTexture.uvTranslation = readVector2(texture.get("uvTranslation"), 0f, 0f);
jsonTexture.uvScaling = readVector2(texture.get("uvScaling"), 1f, 1f);
String textureType = texture.getString("type", null);
if (textureType == null) throw new GdxRuntimeException("Texture needs type.");
jsonTexture.usage = parseTextureUsage(textureType);
if (jsonMaterial.textures == null) jsonMaterial.textures = new Array<ModelTexture>();
jsonMaterial.textures.add(jsonTexture);
}
}
model.materials.add(jsonMaterial);
}
}
}
private int parseTextureUsage (final String value) {
if (value.equalsIgnoreCase("AMBIENT"))
return ModelTexture.USAGE_AMBIENT;
else if (value.equalsIgnoreCase("BUMP"))
return ModelTexture.USAGE_BUMP;
else if (value.equalsIgnoreCase("DIFFUSE"))
return ModelTexture.USAGE_DIFFUSE;
else if (value.equalsIgnoreCase("EMISSIVE"))
return ModelTexture.USAGE_EMISSIVE;
else if (value.equalsIgnoreCase("NONE"))
return ModelTexture.USAGE_NONE;
else if (value.equalsIgnoreCase("NORMAL"))
return ModelTexture.USAGE_NORMAL;
else if (value.equalsIgnoreCase("REFLECTION"))
return ModelTexture.USAGE_REFLECTION;
else if (value.equalsIgnoreCase("SHININESS"))
return ModelTexture.USAGE_SHININESS;
else if (value.equalsIgnoreCase("SPECULAR"))
return ModelTexture.USAGE_SPECULAR;
else if (value.equalsIgnoreCase("TRANSPARENCY")) return ModelTexture.USAGE_TRANSPARENCY;
return ModelTexture.USAGE_UNKNOWN;
}
private Color parseColor (JsonValue colorArray) {
if (colorArray.size >= 3)
return new Color(colorArray.getFloat(0), colorArray.getFloat(1), colorArray.getFloat(2), 1.0f);
else
throw new GdxRuntimeException("Expected Color values <> than three.");
}
private Vector2 readVector2 (JsonValue vectorArray, float x, float y) {
if (vectorArray == null)
return new Vector2(x, y);
else if (vectorArray.size == 2)
return new Vector2(vectorArray.getFloat(0), vectorArray.getFloat(1));
else
throw new GdxRuntimeException("Expected Vector2 values <> than two.");
}
private Array<ModelNode> parseNodes (ModelData model, JsonValue json) {
JsonValue nodes = json.get("nodes");
if (nodes != null) {
model.nodes.ensureCapacity(nodes.size);
for (JsonValue node = nodes.child; node != null; node = node.next) {
model.nodes.add(parseNodesRecursively(node));
}
}
return model.nodes;
}
private final Quaternion tempQ = new Quaternion();
private ModelNode parseNodesRecursively (JsonValue json) {
ModelNode jsonNode = new ModelNode();
String id = json.getString("id", null);
if (id == null) throw new GdxRuntimeException("Node id missing.");
jsonNode.id = id;
JsonValue translation = json.get("translation");
if (translation != null && translation.size != 3) throw new GdxRuntimeException("Node translation incomplete");
jsonNode.translation = translation == null ? null : new Vector3(translation.getFloat(0), translation.getFloat(1),
translation.getFloat(2));
JsonValue rotation = json.get("rotation");
if (rotation != null && rotation.size != 4) throw new GdxRuntimeException("Node rotation incomplete");
jsonNode.rotation = rotation == null ? null : new Quaternion(rotation.getFloat(0), rotation.getFloat(1),
rotation.getFloat(2), rotation.getFloat(3));
JsonValue scale = json.get("scale");
if (scale != null && scale.size != 3) throw new GdxRuntimeException("Node scale incomplete");
jsonNode.scale = scale == null ? null : new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2));
String meshId = json.getString("mesh", null);
if (meshId != null) jsonNode.meshId = meshId;
JsonValue materials = json.get("parts");
if (materials != null) {
jsonNode.parts = new ModelNodePart[materials.size];
int i = 0;
for (JsonValue material = materials.child; material != null; material = material.next, i++) {
ModelNodePart nodePart = new ModelNodePart();
String meshPartId = material.getString("meshpartid", null);
String materialId = material.getString("materialid", null);
if (meshPartId == null || materialId == null) {
throw new GdxRuntimeException("Node " + id + " part is missing meshPartId or materialId");
}
nodePart.materialId = materialId;
nodePart.meshPartId = meshPartId;
JsonValue bones = material.get("bones");
if (bones != null) {
nodePart.bones = new ArrayMap<String, Matrix4>(true, bones.size, String.class, Matrix4.class);
int j = 0;
for (JsonValue bone = bones.child; bone != null; bone = bone.next, j++) {
String nodeId = bone.getString("node", null);
if (nodeId == null) throw new GdxRuntimeException("Bone node ID missing");
Matrix4 transform = new Matrix4();
JsonValue val = bone.get("translation");
if (val != null && val.size >= 3) transform.translate(val.getFloat(0), val.getFloat(1), val.getFloat(2));
val = bone.get("rotation");
if (val != null && val.size >= 4)
transform.rotate(tempQ.set(val.getFloat(0), val.getFloat(1), val.getFloat(2), val.getFloat(3)));
val = bone.get("scale");
if (val != null && val.size >= 3) transform.scale(val.getFloat(0), val.getFloat(1), val.getFloat(2));
nodePart.bones.put(nodeId, transform);
}
}
jsonNode.parts[i] = nodePart;
}
}
JsonValue children = json.get("children");
if (children != null) {
jsonNode.children = new ModelNode[children.size];
int i = 0;
for (JsonValue child = children.child; child != null; child = child.next, i++) {
jsonNode.children[i] = parseNodesRecursively(child);
}
}
return jsonNode;
}
private void parseAnimations (ModelData model, JsonValue json) {
JsonValue animations = json.get("animations");
if (animations == null) return;
model.animations.ensureCapacity(animations.size);
for (JsonValue anim = animations.child; anim != null; anim = anim.next) {
JsonValue nodes = anim.get("bones");
if (nodes == null) continue;
ModelAnimation animation = new ModelAnimation();
model.animations.add(animation);
animation.nodeAnimations.ensureCapacity(nodes.size);
animation.id = anim.getString("id");
for (JsonValue node = nodes.child; node != null; node = node.next) {
ModelNodeAnimation nodeAnim = new ModelNodeAnimation();
animation.nodeAnimations.add(nodeAnim);
nodeAnim.nodeId = node.getString("boneId");
// For backwards compatibility (version 0.1):
JsonValue keyframes = node.get("keyframes");
if (keyframes != null && keyframes.isArray()) {
for (JsonValue keyframe = keyframes.child; keyframe != null; keyframe = keyframe.next) {
final float keytime = keyframe.getFloat("keytime", 0f) / 1000.f;
JsonValue translation = keyframe.get("translation");
if (translation != null && translation.size == 3) {
if (nodeAnim.translation == null)
nodeAnim.translation = new Array<ModelNodeKeyframe<Vector3>>();
ModelNodeKeyframe<Vector3> tkf = new ModelNodeKeyframe<Vector3>();
tkf.keytime = keytime;
tkf.value = new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2));
nodeAnim.translation.add(tkf);
}
JsonValue rotation = keyframe.get("rotation");
if (rotation != null && rotation.size == 4) {
if (nodeAnim.rotation == null)
nodeAnim.rotation = new Array<ModelNodeKeyframe<Quaternion>>();
ModelNodeKeyframe<Quaternion> rkf = new ModelNodeKeyframe<Quaternion>();
rkf.keytime = keytime;
rkf.value = new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3));
nodeAnim.rotation.add(rkf);
}
JsonValue scale = keyframe.get("scale");
if (scale != null && scale.size == 3) {
if (nodeAnim.scaling == null)
nodeAnim.scaling = new Array<ModelNodeKeyframe<Vector3>>();
ModelNodeKeyframe<Vector3> skf = new ModelNodeKeyframe();
skf.keytime = keytime;
skf.value = new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2));
nodeAnim.scaling.add(skf);
}
}
} else { // Version 0.2:
JsonValue translationKF = node.get("translation");
if (translationKF != null && translationKF.isArray()) {
nodeAnim.translation = new Array<ModelNodeKeyframe<Vector3>>();
nodeAnim.translation.ensureCapacity(translationKF.size);
for (JsonValue keyframe = translationKF.child; keyframe != null; keyframe = keyframe.next) {
ModelNodeKeyframe<Vector3> kf = new ModelNodeKeyframe<Vector3>();
nodeAnim.translation.add(kf);
kf.keytime = keyframe.getFloat("keytime", 0f) / 1000.f;
JsonValue translation = keyframe.get("value");
if (translation != null && translation.size >= 3)
kf.value = new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2));
}
}
JsonValue rotationKF = node.get("rotation");
if (rotationKF != null && rotationKF.isArray()) {
nodeAnim.rotation = new Array<ModelNodeKeyframe<Quaternion>>();
nodeAnim.rotation.ensureCapacity(rotationKF.size);
for (JsonValue keyframe = rotationKF.child; keyframe != null; keyframe = keyframe.next) {
ModelNodeKeyframe<Quaternion> kf = new ModelNodeKeyframe<Quaternion>();
nodeAnim.rotation.add(kf);
kf.keytime = keyframe.getFloat("keytime", 0f) / 1000.f;
JsonValue rotation = keyframe.get("value");
if (rotation != null && rotation.size >= 4)
kf.value = new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3));
}
}
JsonValue scalingKF = node.get("scaling");
if (scalingKF != null && scalingKF.isArray()) {
nodeAnim.scaling = new Array<ModelNodeKeyframe<Vector3>>();
nodeAnim.scaling.ensureCapacity(scalingKF.size);
for (JsonValue keyframe = scalingKF.child; keyframe != null; keyframe = keyframe.next) {
ModelNodeKeyframe<Vector3> kf = new ModelNodeKeyframe<Vector3>();
nodeAnim.scaling.add(kf);
kf.keytime = keyframe.getFloat("keytime", 0f) / 1000.f;
JsonValue scaling = keyframe.get("value");
if (scaling != null && scaling.size >= 3)
kf.value = new Vector3(scaling.getFloat(0), scaling.getFloat(1), scaling.getFloat(2));
}
}
}
}
}
}
}