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