1 /*******************************************************************************
\r
2 * Copyright 2011 See AUTHORS file.
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
15 ******************************************************************************/
17 package com.badlogic.gdx.graphics.g3d.loader;
19 import com.badlogic.gdx.assets.AssetLoaderParameters;
20 import com.badlogic.gdx.assets.loaders.FileHandleResolver;
21 import com.badlogic.gdx.assets.loaders.ModelLoader;
22 import com.badlogic.gdx.files.FileHandle;
23 import com.badlogic.gdx.graphics.Color;
24 import com.badlogic.gdx.graphics.GL10;
25 import com.badlogic.gdx.graphics.VertexAttribute;
26 import com.badlogic.gdx.graphics.g3d.Model;
27 import com.badlogic.gdx.graphics.g3d.model.data.ModelAnimation;
28 import com.badlogic.gdx.graphics.g3d.model.data.ModelData;
29 import com.badlogic.gdx.graphics.g3d.model.data.ModelMaterial;
30 import com.badlogic.gdx.graphics.g3d.model.data.ModelMesh;
31 import com.badlogic.gdx.graphics.g3d.model.data.ModelMeshPart;
32 import com.badlogic.gdx.graphics.g3d.model.data.ModelNode;
33 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeAnimation;
34 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeKeyframe;
35 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodePart;
36 import com.badlogic.gdx.graphics.g3d.model.data.ModelTexture;
37 import com.badlogic.gdx.math.Matrix4;
38 import com.badlogic.gdx.math.Quaternion;
39 import com.badlogic.gdx.math.Vector2;
40 import com.badlogic.gdx.math.Vector3;
41 import com.badlogic.gdx.utils.Array;
42 import com.badlogic.gdx.utils.ArrayMap;
43 import com.badlogic.gdx.utils.BaseJsonReader;
44 import com.badlogic.gdx.utils.GdxRuntimeException;
45 import com.badlogic.gdx.utils.JsonValue;
46 import com.badlogic.gdx.utils.UBJsonReader;
48 public class G3dModelLoader extends ModelLoader<AssetLoaderParameters<Model>> {
49 public static final short VERSION_HI = 0;
50 public static final short VERSION_LO = 1;
51 protected final BaseJsonReader reader;
53 public G3dModelLoader(final BaseJsonReader reader) {
57 public G3dModelLoader(BaseJsonReader reader, FileHandleResolver resolver) {
63 public ModelData loadModelData (FileHandle fileHandle, AssetLoaderParameters<Model> parameters) {
64 return parseModel(fileHandle);
67 public ModelData parseModel (FileHandle handle) {
68 JsonValue json = reader.parse(handle);
69 ModelData model = new ModelData();
70 JsonValue version = json.require("version");
71 model.version[0] = (short)version.getInt(0);
72 model.version[1] = (short)version.getInt(1);
73 if (model.version[0] != VERSION_HI || model.version[1] != VERSION_LO)
74 throw new GdxRuntimeException("Model version not supported");
76 model.id = json.getString("id", "");
77 parseMeshes(model, json);
78 parseMaterials(model, json, handle.parent().path());
79 parseNodes(model, json);
80 parseAnimations(model, json);
84 private void parseMeshes (ModelData model, JsonValue json) {
85 JsonValue meshes = json.require("meshes");
87 model.meshes.ensureCapacity(meshes.size);
88 for (JsonValue mesh = meshes.child(); mesh != null; mesh = mesh.next()) {
89 ModelMesh jsonMesh = new ModelMesh();
91 String id = mesh.getString("id", "");
94 JsonValue attributes = mesh.require("attributes");
95 jsonMesh.attributes = parseAttributes(attributes);
97 JsonValue vertices = mesh.require("vertices");
98 float[] verts = new float[vertices.size];
100 for (JsonValue value = vertices.child(); value != null; value = value.next(), j++) {
101 verts[j] = value.asFloat();
103 jsonMesh.vertices = verts;
105 JsonValue meshParts = mesh.require("parts");
106 Array<ModelMeshPart> parts = new Array<ModelMeshPart>();
107 for (JsonValue meshPart = meshParts.child(); meshPart != null; meshPart = meshPart.next()) {
108 ModelMeshPart jsonPart = new ModelMeshPart();
109 String partId = meshPart.getString("id", null);
111 throw new GdxRuntimeException("Not id given for mesh part");
113 for(ModelMeshPart other: parts) {
114 if(other.id.equals(partId)) {
115 throw new GdxRuntimeException("Mesh part with id '" + partId + "' already in defined");
118 jsonPart.id = partId;
120 String type = meshPart.getString("type", null);
122 throw new GdxRuntimeException("No primitive type given for mesh part '" + partId + "'");
124 jsonPart.primitiveType = parseType(type);
126 JsonValue indices = meshPart.require("indices");
127 short[] partIndices = new short[indices.size];
129 for (JsonValue value = indices.child(); value != null; value = value.next(), k++) {
130 partIndices[k] = (short)value.asInt();
132 jsonPart.indices = partIndices;
135 jsonMesh.parts = parts.toArray(ModelMeshPart.class);
136 model.meshes.add(jsonMesh);
140 private int parseType (String type) {
141 if(type.equals("TRIANGLES")) {
142 return GL10.GL_TRIANGLES;
143 } else if(type.equals("LINES")) {
144 return GL10.GL_LINES;
145 } else if(type.equals("POINTS")) {
146 return GL10.GL_POINTS;
147 } else if(type.equals("TRIANGLE_STRIP")) {
148 return GL10.GL_TRIANGLE_STRIP;
149 } else if(type.equals("LINE_STRIP")) {
150 return GL10.GL_LINE_STRIP;
152 throw new GdxRuntimeException("Unknown primitive type '" + type + "', should be one of triangle, trianglestrip, line, linestrip, lineloop or point");
156 private VertexAttribute[] parseAttributes (JsonValue attributes) {
157 Array<VertexAttribute> vertexAttributes = new Array<VertexAttribute>();
159 int blendWeightCount = 0;
160 for (JsonValue value = attributes.child(); value != null; value = value.next()) {
161 String attribute = value.asString();
162 String attr = (String)attribute;
163 if(attr.equals("POSITION")) {
164 vertexAttributes.add(VertexAttribute.Position());
165 } else if(attr.equals("NORMAL")) {
166 vertexAttributes.add(VertexAttribute.Normal());
167 } else if(attr.equals("COLOR")) {
168 vertexAttributes.add(VertexAttribute.ColorUnpacked());
169 } else if(attr.equals("COLORPACKED")) {
170 vertexAttributes.add(VertexAttribute.Color());
171 } else if(attr.equals("TANGENT")) {
172 vertexAttributes.add(VertexAttribute.Tangent());
173 } else if(attr.equals("BINORMAL")) {
174 vertexAttributes.add(VertexAttribute.Binormal());
175 } else if(attr.startsWith("TEXCOORD")) {
176 vertexAttributes.add(VertexAttribute.TexCoords(unit++));
177 } else if(attr.startsWith("BLENDWEIGHT")) {
178 vertexAttributes.add(VertexAttribute.BoneWeight(blendWeightCount++));
180 throw new GdxRuntimeException("Unknown vertex attribute '" + attr + "', should be one of position, normal, uv, tangent or binormal");
183 return vertexAttributes.toArray(VertexAttribute.class);
186 private void parseMaterials (ModelData model, JsonValue json, String materialDir) {
187 JsonValue materials = json.get("materials");
188 if(materials == null) {
189 // we should probably create some default material in this case
192 model.materials.ensureCapacity(materials.size);
193 for (JsonValue material = materials.child(); material != null; material = material.next()) {
194 ModelMaterial jsonMaterial = new ModelMaterial();
196 String id = material.getString("id", null);
198 throw new GdxRuntimeException("Material needs an id.");
200 jsonMaterial.id = id;
202 // Read material colors
203 final JsonValue diffuse = material.get("diffuse");
205 jsonMaterial.diffuse = parseColor(diffuse);
206 final JsonValue ambient = material.get("ambient");
208 jsonMaterial.ambient = parseColor(ambient);
209 final JsonValue emissive= material.get("emissive");
211 jsonMaterial.emissive = parseColor(emissive);
212 final JsonValue specular= material.get("specular");
214 jsonMaterial.specular = parseColor(specular);
215 final JsonValue reflection= material.get("reflection");
216 if (reflection!= null)
217 jsonMaterial.reflection = parseColor(reflection);
219 jsonMaterial.shininess = material.getFloat("shininess", 0.0f);
221 jsonMaterial.opacity = material.getFloat("opacity", 1.0f);
224 JsonValue textures = material.get("textures");
225 if(textures != null){
226 for (JsonValue texture = textures.child(); texture != null; texture = texture.next()) {
227 ModelTexture jsonTexture = new ModelTexture();
229 String textureId = texture.getString("id", null);
230 if(textureId == null)
231 throw new GdxRuntimeException("Texture has no id.");
232 jsonTexture.id = textureId;
234 String fileName = texture.getString("filename", null);
236 throw new GdxRuntimeException("Texture needs filename.");
237 jsonTexture.fileName = materialDir + (materialDir.length() == 0 || materialDir.endsWith("/") ? "" : "/") + fileName;
239 jsonTexture.uvTranslation = readVector2(texture.get("uvTranslation"), 0f, 0f);
240 jsonTexture.uvScaling = readVector2(texture.get("uvScaling"), 1f, 1f);
242 String textureType = texture.getString("type", null);
243 if(textureType == null)
244 throw new GdxRuntimeException("Texture needs type.");
246 jsonTexture.usage = parseTextureUsage(textureType);
248 if(jsonMaterial.textures == null)
249 jsonMaterial.textures = new Array<ModelTexture>();
250 jsonMaterial.textures.add(jsonTexture);
254 model.materials.add(jsonMaterial);
259 private int parseTextureUsage(final String value) {
260 if (value.equalsIgnoreCase("AMBIENT"))
261 return ModelTexture.USAGE_AMBIENT;
262 else if (value.equalsIgnoreCase("BUMP"))
263 return ModelTexture.USAGE_BUMP;
264 else if (value.equalsIgnoreCase("DIFFUSE"))
265 return ModelTexture.USAGE_DIFFUSE;
266 else if (value.equalsIgnoreCase("EMISSIVE"))
267 return ModelTexture.USAGE_EMISSIVE;
268 else if (value.equalsIgnoreCase("NONE"))
269 return ModelTexture.USAGE_NONE;
270 else if (value.equalsIgnoreCase("NORMAL"))
271 return ModelTexture.USAGE_NORMAL;
272 else if (value.equalsIgnoreCase("REFLECTION"))
273 return ModelTexture.USAGE_REFLECTION;
274 else if (value.equalsIgnoreCase("SHININESS"))
275 return ModelTexture.USAGE_SHININESS;
276 else if (value.equalsIgnoreCase("SPECULAR"))
277 return ModelTexture.USAGE_SPECULAR;
278 else if (value.equalsIgnoreCase("TRANSPARENCY"))
279 return ModelTexture.USAGE_TRANSPARENCY;
280 return ModelTexture.USAGE_UNKNOWN;
283 private Color parseColor (JsonValue colorArray) {
284 if(colorArray.size >= 3)
285 return new Color(colorArray.getFloat(0), colorArray.getFloat(1), colorArray.getFloat(2), 1.0f);
287 throw new GdxRuntimeException("Expected Color values <> than three.");
290 private Vector2 readVector2 (JsonValue vectorArray, float x, float y) {
291 if(vectorArray == null)
292 return new Vector2(x, y);
293 else if(vectorArray.size == 2)
294 return new Vector2(vectorArray.getFloat(0), vectorArray.getFloat(1));
296 throw new GdxRuntimeException("Expected Vector2 values <> than two.");
299 private Array<ModelNode> parseNodes (ModelData model, JsonValue json) {
300 JsonValue nodes = json.get("nodes");
302 throw new GdxRuntimeException("At least one node is required.");
305 model.nodes.ensureCapacity(nodes.size);
306 for (JsonValue node = nodes.child(); node != null; node = node.next()) {
307 model.nodes.add(parseNodesRecursively(node));
312 private final Quaternion tempQ = new Quaternion();
313 private ModelNode parseNodesRecursively(JsonValue json){
314 ModelNode jsonNode = new ModelNode();
316 String id = json.getString("id", null);
318 throw new GdxRuntimeException("Node id missing.");
321 JsonValue translation = json.get("translation");
322 if (translation != null && translation.size != 3)
323 throw new GdxRuntimeException("Node translation incomplete");
324 jsonNode.translation = translation == null ? null : new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2));
326 JsonValue rotation = json.get("rotation");
327 if(rotation != null && rotation.size != 4)
328 throw new GdxRuntimeException("Node rotation incomplete");
329 jsonNode.rotation = rotation == null ? null : new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3));
331 JsonValue scale = json.get("scale");
332 if(scale != null && scale.size != 3)
333 throw new GdxRuntimeException("Node scale incomplete");
334 jsonNode.scale = scale == null ? null : new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2));
336 String meshId = json.getString("mesh", null);
338 jsonNode.meshId = meshId;
340 JsonValue materials = json.get("parts");
341 if(materials != null){
342 jsonNode.parts = new ModelNodePart[materials.size];
344 for (JsonValue material = materials.child(); material != null; material = material.next(), i++) {
345 ModelNodePart nodePart = new ModelNodePart();
347 String meshPartId = material.getString("meshpartid", null);
348 String materialId = material.getString("materialid", null);
349 if(meshPartId == null || materialId == null){
350 throw new GdxRuntimeException("Node "+id+" part is missing meshPartId or materialId");
352 nodePart.materialId = materialId;
353 nodePart.meshPartId = meshPartId;
355 JsonValue bones = material.get("bones");
357 nodePart.bones = new ArrayMap<String, Matrix4>(true, bones.size, String.class, Matrix4.class);
359 for (JsonValue bone = bones.child(); bone != null; bone = bone.next(), j++) {
360 String nodeId = bone.getString("node", null);
362 throw new GdxRuntimeException("Bone node ID missing");
364 Matrix4 transform = new Matrix4();
366 JsonValue val = bone.get("translation");
367 if (val != null && val.size >= 3)
368 transform.translate(val.getFloat(0), val.getFloat(1), val.getFloat(2));
370 val = bone.get("rotation");
371 if(val != null && val.size >= 4)
372 transform.rotate(tempQ.set(val.getFloat(0), val.getFloat(1), val.getFloat(2), val.getFloat(3)));
374 val = bone.get("scale");
375 if(val != null && val.size >= 3)
376 transform.scale(val.getFloat(0), val.getFloat(1), val.getFloat(2));
378 nodePart.bones.put(nodeId, transform);
382 jsonNode.parts[i] = nodePart;
386 JsonValue children = json.get("children");
387 if(children != null){
388 jsonNode.children = new ModelNode[children.size];
391 for (JsonValue child = children.child(); child != null; child = child.next(), i++) {
392 jsonNode.children[i] = parseNodesRecursively(child);
399 private void parseAnimations (ModelData model, JsonValue json) {
400 JsonValue animations = json.get("animations");
401 if(animations == null)
404 model.animations.ensureCapacity(animations.size);
406 for (JsonValue anim = animations.child(); anim != null; anim = anim.next()) {
407 JsonValue nodes = anim.get("bones");
410 ModelAnimation animation = new ModelAnimation();
411 model.animations.add(animation);
412 animation.nodeAnimations.ensureCapacity(nodes.size);
413 animation.id = anim.getString("id");
414 for (JsonValue node = nodes.child(); node != null; node = node.next()) {
415 JsonValue keyframes = node.get("keyframes");
417 ModelNodeAnimation nodeAnim = new ModelNodeAnimation();
418 animation.nodeAnimations.add(nodeAnim);
419 nodeAnim.nodeId = node.getString("boneId");
420 nodeAnim.keyframes.ensureCapacity(keyframes.size);
422 for (JsonValue keyframe = keyframes.child(); keyframe != null; keyframe = keyframe.next()) {
423 ModelNodeKeyframe kf = new ModelNodeKeyframe();
424 nodeAnim.keyframes.add(kf);
425 kf.keytime = keyframe.getFloat("keytime") / 1000.f;
426 JsonValue translation = keyframe.get("translation");
427 if (translation != null && translation.size == 3)
428 kf.translation = new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2));
429 JsonValue rotation = keyframe.get("rotation");
430 if (rotation != null && rotation.size == 4)
431 kf.rotation = new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3));
432 JsonValue scale = keyframe.get("scale");
433 if (scale != null && scale.size == 3)
434 kf.scale = new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2));