• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.graphics.g3d.loader;
18 
19 import com.badlogic.gdx.assets.loaders.FileHandleResolver;
20 import com.badlogic.gdx.assets.loaders.ModelLoader;
21 import com.badlogic.gdx.files.FileHandle;
22 import com.badlogic.gdx.graphics.Color;
23 import com.badlogic.gdx.graphics.GL20;
24 import com.badlogic.gdx.graphics.VertexAttribute;
25 import com.badlogic.gdx.graphics.g3d.model.data.ModelAnimation;
26 import com.badlogic.gdx.graphics.g3d.model.data.ModelData;
27 import com.badlogic.gdx.graphics.g3d.model.data.ModelMaterial;
28 import com.badlogic.gdx.graphics.g3d.model.data.ModelMesh;
29 import com.badlogic.gdx.graphics.g3d.model.data.ModelMeshPart;
30 import com.badlogic.gdx.graphics.g3d.model.data.ModelNode;
31 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeAnimation;
32 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeKeyframe;
33 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodePart;
34 import com.badlogic.gdx.graphics.g3d.model.data.ModelTexture;
35 import com.badlogic.gdx.math.Matrix4;
36 import com.badlogic.gdx.math.Quaternion;
37 import com.badlogic.gdx.math.Vector2;
38 import com.badlogic.gdx.math.Vector3;
39 import com.badlogic.gdx.utils.Array;
40 import com.badlogic.gdx.utils.ArrayMap;
41 import com.badlogic.gdx.utils.BaseJsonReader;
42 import com.badlogic.gdx.utils.GdxRuntimeException;
43 import com.badlogic.gdx.utils.JsonValue;
44 
45 public class G3dModelLoader extends ModelLoader<ModelLoader.ModelParameters> {
46 	public static final short VERSION_HI = 0;
47 	public static final short VERSION_LO = 1;
48 	protected final BaseJsonReader reader;
49 
G3dModelLoader(final BaseJsonReader reader)50 	public G3dModelLoader (final BaseJsonReader reader) {
51 		this(reader, null);
52 	}
53 
G3dModelLoader(BaseJsonReader reader, FileHandleResolver resolver)54 	public G3dModelLoader (BaseJsonReader reader, FileHandleResolver resolver) {
55 		super(resolver);
56 		this.reader = reader;
57 	}
58 
59 	@Override
loadModelData(FileHandle fileHandle, ModelLoader.ModelParameters parameters)60 	public ModelData loadModelData (FileHandle fileHandle, ModelLoader.ModelParameters parameters) {
61 		return parseModel(fileHandle);
62 	}
63 
parseModel(FileHandle handle)64 	public ModelData parseModel (FileHandle handle) {
65 		JsonValue json = reader.parse(handle);
66 		ModelData model = new ModelData();
67 		JsonValue version = json.require("version");
68 		model.version[0] = version.getShort(0);
69 		model.version[1] = version.getShort(1);
70 		if (model.version[0] != VERSION_HI || model.version[1] != VERSION_LO)
71 			throw new GdxRuntimeException("Model version not supported");
72 
73 		model.id = json.getString("id", "");
74 		parseMeshes(model, json);
75 		parseMaterials(model, json, handle.parent().path());
76 		parseNodes(model, json);
77 		parseAnimations(model, json);
78 		return model;
79 	}
80 
parseMeshes(ModelData model, JsonValue json)81 	private void parseMeshes (ModelData model, JsonValue json) {
82 		JsonValue meshes = json.get("meshes");
83 		if (meshes != null) {
84 
85 			model.meshes.ensureCapacity(meshes.size);
86 			for (JsonValue mesh = meshes.child; mesh != null; mesh = mesh.next) {
87 				ModelMesh jsonMesh = new ModelMesh();
88 
89 				String id = mesh.getString("id", "");
90 				jsonMesh.id = id;
91 
92 				JsonValue attributes = mesh.require("attributes");
93 				jsonMesh.attributes = parseAttributes(attributes);
94 				jsonMesh.vertices = mesh.require("vertices").asFloatArray();
95 
96 				JsonValue meshParts = mesh.require("parts");
97 				Array<ModelMeshPart> parts = new Array<ModelMeshPart>();
98 				for (JsonValue meshPart = meshParts.child; meshPart != null; meshPart = meshPart.next) {
99 					ModelMeshPart jsonPart = new ModelMeshPart();
100 					String partId = meshPart.getString("id", null);
101 					if (partId == null) {
102 						throw new GdxRuntimeException("Not id given for mesh part");
103 					}
104 					for (ModelMeshPart other : parts) {
105 						if (other.id.equals(partId)) {
106 							throw new GdxRuntimeException("Mesh part with id '" + partId + "' already in defined");
107 						}
108 					}
109 					jsonPart.id = partId;
110 
111 					String type = meshPart.getString("type", null);
112 					if (type == null) {
113 						throw new GdxRuntimeException("No primitive type given for mesh part '" + partId + "'");
114 					}
115 					jsonPart.primitiveType = parseType(type);
116 
117 					jsonPart.indices = meshPart.require("indices").asShortArray();
118 					parts.add(jsonPart);
119 				}
120 				jsonMesh.parts = parts.toArray(ModelMeshPart.class);
121 				model.meshes.add(jsonMesh);
122 			}
123 		}
124 	}
125 
parseType(String type)126 	private int parseType (String type) {
127 		if (type.equals("TRIANGLES")) {
128 			return GL20.GL_TRIANGLES;
129 		} else if (type.equals("LINES")) {
130 			return GL20.GL_LINES;
131 		} else if (type.equals("POINTS")) {
132 			return GL20.GL_POINTS;
133 		} else if (type.equals("TRIANGLE_STRIP")) {
134 			return GL20.GL_TRIANGLE_STRIP;
135 		} else if (type.equals("LINE_STRIP")) {
136 			return GL20.GL_LINE_STRIP;
137 		} else {
138 			throw new GdxRuntimeException("Unknown primitive type '" + type
139 				+ "', should be one of triangle, trianglestrip, line, linestrip, lineloop or point");
140 		}
141 	}
142 
parseAttributes(JsonValue attributes)143 	private VertexAttribute[] parseAttributes (JsonValue attributes) {
144 		Array<VertexAttribute> vertexAttributes = new Array<VertexAttribute>();
145 		int unit = 0;
146 		int blendWeightCount = 0;
147 		for (JsonValue value = attributes.child; value != null; value = value.next) {
148 			String attribute = value.asString();
149 			String attr = (String)attribute;
150 			if (attr.equals("POSITION")) {
151 				vertexAttributes.add(VertexAttribute.Position());
152 			} else if (attr.equals("NORMAL")) {
153 				vertexAttributes.add(VertexAttribute.Normal());
154 			} else if (attr.equals("COLOR")) {
155 				vertexAttributes.add(VertexAttribute.ColorUnpacked());
156 			} else if (attr.equals("COLORPACKED")) {
157 				vertexAttributes.add(VertexAttribute.ColorPacked());
158 			} else if (attr.equals("TANGENT")) {
159 				vertexAttributes.add(VertexAttribute.Tangent());
160 			} else if (attr.equals("BINORMAL")) {
161 				vertexAttributes.add(VertexAttribute.Binormal());
162 			} else if (attr.startsWith("TEXCOORD")) {
163 				vertexAttributes.add(VertexAttribute.TexCoords(unit++));
164 			} else if (attr.startsWith("BLENDWEIGHT")) {
165 				vertexAttributes.add(VertexAttribute.BoneWeight(blendWeightCount++));
166 			} else {
167 				throw new GdxRuntimeException("Unknown vertex attribute '" + attr
168 					+ "', should be one of position, normal, uv, tangent or binormal");
169 			}
170 		}
171 		return vertexAttributes.toArray(VertexAttribute.class);
172 	}
173 
parseMaterials(ModelData model, JsonValue json, String materialDir)174 	private void parseMaterials (ModelData model, JsonValue json, String materialDir) {
175 		JsonValue materials = json.get("materials");
176 		if (materials == null) {
177 			// we should probably create some default material in this case
178 		} else {
179 			model.materials.ensureCapacity(materials.size);
180 			for (JsonValue material = materials.child; material != null; material = material.next) {
181 				ModelMaterial jsonMaterial = new ModelMaterial();
182 
183 				String id = material.getString("id", null);
184 				if (id == null) throw new GdxRuntimeException("Material needs an id.");
185 
186 				jsonMaterial.id = id;
187 
188 				// Read material colors
189 				final JsonValue diffuse = material.get("diffuse");
190 				if (diffuse != null) jsonMaterial.diffuse = parseColor(diffuse);
191 				final JsonValue ambient = material.get("ambient");
192 				if (ambient != null) jsonMaterial.ambient = parseColor(ambient);
193 				final JsonValue emissive = material.get("emissive");
194 				if (emissive != null) jsonMaterial.emissive = parseColor(emissive);
195 				final JsonValue specular = material.get("specular");
196 				if (specular != null) jsonMaterial.specular = parseColor(specular);
197 				final JsonValue reflection = material.get("reflection");
198 				if (reflection != null) jsonMaterial.reflection = parseColor(reflection);
199 				// Read shininess
200 				jsonMaterial.shininess = material.getFloat("shininess", 0.0f);
201 				// Read opacity
202 				jsonMaterial.opacity = material.getFloat("opacity", 1.0f);
203 
204 				// Read textures
205 				JsonValue textures = material.get("textures");
206 				if (textures != null) {
207 					for (JsonValue texture = textures.child; texture != null; texture = texture.next) {
208 						ModelTexture jsonTexture = new ModelTexture();
209 
210 						String textureId = texture.getString("id", null);
211 						if (textureId == null) throw new GdxRuntimeException("Texture has no id.");
212 						jsonTexture.id = textureId;
213 
214 						String fileName = texture.getString("filename", null);
215 						if (fileName == null) throw new GdxRuntimeException("Texture needs filename.");
216 						jsonTexture.fileName = materialDir + (materialDir.length() == 0 || materialDir.endsWith("/") ? "" : "/")
217 							+ fileName;
218 
219 						jsonTexture.uvTranslation = readVector2(texture.get("uvTranslation"), 0f, 0f);
220 						jsonTexture.uvScaling = readVector2(texture.get("uvScaling"), 1f, 1f);
221 
222 						String textureType = texture.getString("type", null);
223 						if (textureType == null) throw new GdxRuntimeException("Texture needs type.");
224 
225 						jsonTexture.usage = parseTextureUsage(textureType);
226 
227 						if (jsonMaterial.textures == null) jsonMaterial.textures = new Array<ModelTexture>();
228 						jsonMaterial.textures.add(jsonTexture);
229 					}
230 				}
231 
232 				model.materials.add(jsonMaterial);
233 			}
234 		}
235 	}
236 
parseTextureUsage(final String value)237 	private int parseTextureUsage (final String value) {
238 		if (value.equalsIgnoreCase("AMBIENT"))
239 			return ModelTexture.USAGE_AMBIENT;
240 		else if (value.equalsIgnoreCase("BUMP"))
241 			return ModelTexture.USAGE_BUMP;
242 		else if (value.equalsIgnoreCase("DIFFUSE"))
243 			return ModelTexture.USAGE_DIFFUSE;
244 		else if (value.equalsIgnoreCase("EMISSIVE"))
245 			return ModelTexture.USAGE_EMISSIVE;
246 		else if (value.equalsIgnoreCase("NONE"))
247 			return ModelTexture.USAGE_NONE;
248 		else if (value.equalsIgnoreCase("NORMAL"))
249 			return ModelTexture.USAGE_NORMAL;
250 		else if (value.equalsIgnoreCase("REFLECTION"))
251 			return ModelTexture.USAGE_REFLECTION;
252 		else if (value.equalsIgnoreCase("SHININESS"))
253 			return ModelTexture.USAGE_SHININESS;
254 		else if (value.equalsIgnoreCase("SPECULAR"))
255 			return ModelTexture.USAGE_SPECULAR;
256 		else if (value.equalsIgnoreCase("TRANSPARENCY")) return ModelTexture.USAGE_TRANSPARENCY;
257 		return ModelTexture.USAGE_UNKNOWN;
258 	}
259 
parseColor(JsonValue colorArray)260 	private Color parseColor (JsonValue colorArray) {
261 		if (colorArray.size >= 3)
262 			return new Color(colorArray.getFloat(0), colorArray.getFloat(1), colorArray.getFloat(2), 1.0f);
263 		else
264 			throw new GdxRuntimeException("Expected Color values <> than three.");
265 	}
266 
readVector2(JsonValue vectorArray, float x, float y)267 	private Vector2 readVector2 (JsonValue vectorArray, float x, float y) {
268 		if (vectorArray == null)
269 			return new Vector2(x, y);
270 		else if (vectorArray.size == 2)
271 			return new Vector2(vectorArray.getFloat(0), vectorArray.getFloat(1));
272 		else
273 			throw new GdxRuntimeException("Expected Vector2 values <> than two.");
274 	}
275 
parseNodes(ModelData model, JsonValue json)276 	private Array<ModelNode> parseNodes (ModelData model, JsonValue json) {
277 		JsonValue nodes = json.get("nodes");
278 		if (nodes != null) {
279 			model.nodes.ensureCapacity(nodes.size);
280 			for (JsonValue node = nodes.child; node != null; node = node.next) {
281 				model.nodes.add(parseNodesRecursively(node));
282 			}
283 		}
284 
285 		return model.nodes;
286 	}
287 
288 	private final Quaternion tempQ = new Quaternion();
289 
parseNodesRecursively(JsonValue json)290 	private ModelNode parseNodesRecursively (JsonValue json) {
291 		ModelNode jsonNode = new ModelNode();
292 
293 		String id = json.getString("id", null);
294 		if (id == null) throw new GdxRuntimeException("Node id missing.");
295 		jsonNode.id = id;
296 
297 		JsonValue translation = json.get("translation");
298 		if (translation != null && translation.size != 3) throw new GdxRuntimeException("Node translation incomplete");
299 		jsonNode.translation = translation == null ? null : new Vector3(translation.getFloat(0), translation.getFloat(1),
300 			translation.getFloat(2));
301 
302 		JsonValue rotation = json.get("rotation");
303 		if (rotation != null && rotation.size != 4) throw new GdxRuntimeException("Node rotation incomplete");
304 		jsonNode.rotation = rotation == null ? null : new Quaternion(rotation.getFloat(0), rotation.getFloat(1),
305 			rotation.getFloat(2), rotation.getFloat(3));
306 
307 		JsonValue scale = json.get("scale");
308 		if (scale != null && scale.size != 3) throw new GdxRuntimeException("Node scale incomplete");
309 		jsonNode.scale = scale == null ? null : new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2));
310 
311 		String meshId = json.getString("mesh", null);
312 		if (meshId != null) jsonNode.meshId = meshId;
313 
314 		JsonValue materials = json.get("parts");
315 		if (materials != null) {
316 			jsonNode.parts = new ModelNodePart[materials.size];
317 			int i = 0;
318 			for (JsonValue material = materials.child; material != null; material = material.next, i++) {
319 				ModelNodePart nodePart = new ModelNodePart();
320 
321 				String meshPartId = material.getString("meshpartid", null);
322 				String materialId = material.getString("materialid", null);
323 				if (meshPartId == null || materialId == null) {
324 					throw new GdxRuntimeException("Node " + id + " part is missing meshPartId or materialId");
325 				}
326 				nodePart.materialId = materialId;
327 				nodePart.meshPartId = meshPartId;
328 
329 				JsonValue bones = material.get("bones");
330 				if (bones != null) {
331 					nodePart.bones = new ArrayMap<String, Matrix4>(true, bones.size, String.class, Matrix4.class);
332 					int j = 0;
333 					for (JsonValue bone = bones.child; bone != null; bone = bone.next, j++) {
334 						String nodeId = bone.getString("node", null);
335 						if (nodeId == null) throw new GdxRuntimeException("Bone node ID missing");
336 
337 						Matrix4 transform = new Matrix4();
338 
339 						JsonValue val = bone.get("translation");
340 						if (val != null && val.size >= 3) transform.translate(val.getFloat(0), val.getFloat(1), val.getFloat(2));
341 
342 						val = bone.get("rotation");
343 						if (val != null && val.size >= 4)
344 							transform.rotate(tempQ.set(val.getFloat(0), val.getFloat(1), val.getFloat(2), val.getFloat(3)));
345 
346 						val = bone.get("scale");
347 						if (val != null && val.size >= 3) transform.scale(val.getFloat(0), val.getFloat(1), val.getFloat(2));
348 
349 						nodePart.bones.put(nodeId, transform);
350 					}
351 				}
352 
353 				jsonNode.parts[i] = nodePart;
354 			}
355 		}
356 
357 		JsonValue children = json.get("children");
358 		if (children != null) {
359 			jsonNode.children = new ModelNode[children.size];
360 
361 			int i = 0;
362 			for (JsonValue child = children.child; child != null; child = child.next, i++) {
363 				jsonNode.children[i] = parseNodesRecursively(child);
364 			}
365 		}
366 
367 		return jsonNode;
368 	}
369 
parseAnimations(ModelData model, JsonValue json)370 	private void parseAnimations (ModelData model, JsonValue json) {
371 		JsonValue animations = json.get("animations");
372 		if (animations == null) return;
373 
374 		model.animations.ensureCapacity(animations.size);
375 
376 		for (JsonValue anim = animations.child; anim != null; anim = anim.next) {
377 			JsonValue nodes = anim.get("bones");
378 			if (nodes == null) continue;
379 			ModelAnimation animation = new ModelAnimation();
380 			model.animations.add(animation);
381 			animation.nodeAnimations.ensureCapacity(nodes.size);
382 			animation.id = anim.getString("id");
383 			for (JsonValue node = nodes.child; node != null; node = node.next) {
384 				ModelNodeAnimation nodeAnim = new ModelNodeAnimation();
385 				animation.nodeAnimations.add(nodeAnim);
386 				nodeAnim.nodeId = node.getString("boneId");
387 
388 				// For backwards compatibility (version 0.1):
389 				JsonValue keyframes = node.get("keyframes");
390 				if (keyframes != null && keyframes.isArray()) {
391 					for (JsonValue keyframe = keyframes.child; keyframe != null; keyframe = keyframe.next) {
392 						final float keytime = keyframe.getFloat("keytime", 0f) / 1000.f;
393 						JsonValue translation = keyframe.get("translation");
394 						if (translation != null && translation.size == 3) {
395 							if (nodeAnim.translation == null)
396 								nodeAnim.translation = new Array<ModelNodeKeyframe<Vector3>>();
397 							ModelNodeKeyframe<Vector3> tkf = new ModelNodeKeyframe<Vector3>();
398 							tkf.keytime = keytime;
399 							tkf.value = new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2));
400 							nodeAnim.translation.add(tkf);
401 						}
402 						JsonValue rotation = keyframe.get("rotation");
403 						if (rotation != null && rotation.size == 4) {
404 							if (nodeAnim.rotation == null)
405 								nodeAnim.rotation = new Array<ModelNodeKeyframe<Quaternion>>();
406 							ModelNodeKeyframe<Quaternion> rkf = new ModelNodeKeyframe<Quaternion>();
407 							rkf.keytime = keytime;
408 							rkf.value = new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3));
409 							nodeAnim.rotation.add(rkf);
410 						}
411 						JsonValue scale = keyframe.get("scale");
412 						if (scale != null && scale.size == 3) {
413 							if (nodeAnim.scaling == null)
414 								nodeAnim.scaling = new Array<ModelNodeKeyframe<Vector3>>();
415 							ModelNodeKeyframe<Vector3> skf = new ModelNodeKeyframe();
416 							skf.keytime = keytime;
417 							skf.value = new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2));
418 							nodeAnim.scaling.add(skf);
419 						}
420 					}
421 				} else { // Version 0.2:
422 					JsonValue translationKF = node.get("translation");
423 					if (translationKF != null && translationKF.isArray()) {
424 						nodeAnim.translation = new Array<ModelNodeKeyframe<Vector3>>();
425 						nodeAnim.translation.ensureCapacity(translationKF.size);
426 						for (JsonValue keyframe = translationKF.child; keyframe != null; keyframe = keyframe.next) {
427 							ModelNodeKeyframe<Vector3> kf = new ModelNodeKeyframe<Vector3>();
428 							nodeAnim.translation.add(kf);
429 							kf.keytime = keyframe.getFloat("keytime", 0f) / 1000.f;
430 							JsonValue translation = keyframe.get("value");
431 							if (translation != null && translation.size >= 3)
432 								kf.value = new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2));
433 						}
434 					}
435 
436 
437 					JsonValue rotationKF = node.get("rotation");
438 					if (rotationKF != null && rotationKF.isArray()) {
439 						nodeAnim.rotation = new Array<ModelNodeKeyframe<Quaternion>>();
440 						nodeAnim.rotation.ensureCapacity(rotationKF.size);
441 						for (JsonValue keyframe = rotationKF.child; keyframe != null; keyframe = keyframe.next) {
442 							ModelNodeKeyframe<Quaternion> kf = new ModelNodeKeyframe<Quaternion>();
443 							nodeAnim.rotation.add(kf);
444 							kf.keytime = keyframe.getFloat("keytime", 0f) / 1000.f;
445 							JsonValue rotation = keyframe.get("value");
446 							if (rotation != null && rotation.size >= 4)
447 								kf.value = new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3));
448 						}
449 					}
450 
451 					JsonValue scalingKF = node.get("scaling");
452 					if (scalingKF != null && scalingKF.isArray()) {
453 						nodeAnim.scaling = new Array<ModelNodeKeyframe<Vector3>>();
454 						nodeAnim.scaling.ensureCapacity(scalingKF.size);
455 						for (JsonValue keyframe = scalingKF.child; keyframe != null; keyframe = keyframe.next) {
456 							ModelNodeKeyframe<Vector3> kf = new ModelNodeKeyframe<Vector3>();
457 							nodeAnim.scaling.add(kf);
458 							kf.keytime = keyframe.getFloat("keytime", 0f) / 1000.f;
459 							JsonValue scaling = keyframe.get("value");
460 							if (scaling != null && scaling.size >= 3)
461 								kf.value = new Vector3(scaling.getFloat(0), scaling.getFloat(1), scaling.getFloat(2));
462 						}
463 					}
464 				}
465 			}
466 		}
467 	}
468 }
469