Home | History | Annotate | Download | only in loader
      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 
     50 	public G3dModelLoader (final BaseJsonReader reader) {
     51 		this(reader, null);
     52 	}
     53 
     54 	public G3dModelLoader (BaseJsonReader reader, FileHandleResolver resolver) {
     55 		super(resolver);
     56 		this.reader = reader;
     57 	}
     58 
     59 	@Override
     60 	public ModelData loadModelData (FileHandle fileHandle, ModelLoader.ModelParameters parameters) {
     61 		return parseModel(fileHandle);
     62 	}
     63 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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