1 package com.badlogic.gdx.graphics.g3d.loader;
3 import com.badlogic.gdx.assets.AssetLoaderParameters;
4 import com.badlogic.gdx.assets.loaders.FileHandleResolver;
5 import com.badlogic.gdx.assets.loaders.ModelLoader;
6 import com.badlogic.gdx.files.FileHandle;
7 import com.badlogic.gdx.graphics.Color;
8 import com.badlogic.gdx.graphics.GL10;
9 import com.badlogic.gdx.graphics.VertexAttribute;
10 import com.badlogic.gdx.graphics.g3d.Model;
11 import com.badlogic.gdx.graphics.g3d.model.data.ModelAnimation;
12 import com.badlogic.gdx.graphics.g3d.model.data.ModelData;
13 import com.badlogic.gdx.graphics.g3d.model.data.ModelMaterial;
14 import com.badlogic.gdx.graphics.g3d.model.data.ModelMesh;
15 import com.badlogic.gdx.graphics.g3d.model.data.ModelMeshPart;
16 import com.badlogic.gdx.graphics.g3d.model.data.ModelNode;
17 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeAnimation;
18 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeKeyframe;
19 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodePart;
20 import com.badlogic.gdx.graphics.g3d.model.data.ModelTexture;
21 import com.badlogic.gdx.math.Matrix4;
22 import com.badlogic.gdx.math.Quaternion;
23 import com.badlogic.gdx.math.Vector2;
24 import com.badlogic.gdx.math.Vector3;
25 import com.badlogic.gdx.utils.Array;
26 import com.badlogic.gdx.utils.ArrayMap;
27 import com.badlogic.gdx.utils.BaseJsonReader;
28 import com.badlogic.gdx.utils.GdxRuntimeException;
29 import com.badlogic.gdx.utils.JsonValue;
30 import com.badlogic.gdx.utils.UBJsonReader;
32 public class G3dModelLoader extends ModelLoader<AssetLoaderParameters<Model>> {
33 public static final short VERSION_HI = 0;
34 public static final short VERSION_LO = 1;
35 protected final BaseJsonReader reader;
37 public G3dModelLoader(final BaseJsonReader reader) {
41 public G3dModelLoader(BaseJsonReader reader, FileHandleResolver resolver) {
47 public ModelData loadModelData (FileHandle fileHandle, AssetLoaderParameters<Model> parameters) {
48 return parseModel(fileHandle);
51 public ModelData parseModel (FileHandle handle) {
52 JsonValue json = reader.parse(handle);
53 ModelData model = new ModelData();
54 JsonValue version = json.require("version");
55 model.version[0] = (short)version.getInt(0);
56 model.version[1] = (short)version.getInt(1);
57 if (model.version[0] != VERSION_HI || model.version[1] != VERSION_LO)
58 throw new GdxRuntimeException("Model version not supported");
60 model.id = json.getString("id", "");
61 parseMeshes(model, json);
62 parseMaterials(model, json, handle.parent().path());
63 parseNodes(model, json);
64 parseAnimations(model, json);
69 private void parseMeshes (ModelData model, JsonValue json) {
70 JsonValue meshes = json.require("meshes");
72 model.meshes.ensureCapacity(meshes.size());
73 for (JsonValue mesh = meshes.child(); mesh != null; mesh = mesh.next()) {
74 ModelMesh jsonMesh = new ModelMesh();
76 String id = mesh.getString("id", "");
79 JsonValue attributes = mesh.require("attributes");
80 jsonMesh.attributes = parseAttributes(attributes);
82 JsonValue vertices = mesh.require("vertices");
83 float[] verts = new float[vertices.size()];
85 for (JsonValue value = vertices.child(); value != null; value = value.next(), j++) {
86 verts[j] = value.asFloat();
88 jsonMesh.vertices = verts;
90 JsonValue meshParts = mesh.require("parts");
91 Array<ModelMeshPart> parts = new Array<ModelMeshPart>();
92 for (JsonValue meshPart = meshParts.child(); meshPart != null; meshPart = meshPart.next()) {
93 ModelMeshPart jsonPart = new ModelMeshPart();
94 String partId = meshPart.getString("id", null);
96 throw new GdxRuntimeException("Not id given for mesh part");
98 for(ModelMeshPart other: parts) {
99 if(other.id.equals(partId)) {
100 throw new GdxRuntimeException("Mesh part with id '" + partId + "' already in defined");
103 jsonPart.id = partId;
105 String type = meshPart.getString("type", null);
107 throw new GdxRuntimeException("No primitive type given for mesh part '" + partId + "'");
109 jsonPart.primitiveType = parseType(type);
111 JsonValue indices = meshPart.require("indices");
112 short[] partIndices = new short[indices.size()];
114 for (JsonValue value = indices.child(); value != null; value = value.next(), k++) {
115 partIndices[k] = (short)value.asInt();
117 jsonPart.indices = partIndices;
120 jsonMesh.parts = parts.toArray(ModelMeshPart.class);
121 model.meshes.add(jsonMesh);
125 private int parseType (String type) {
126 if(type.equals("TRIANGLES")) {
127 return GL10.GL_TRIANGLES;
128 } else if(type.equals("LINES")) {
129 return GL10.GL_LINES;
130 } else if(type.equals("POINTS")) {
131 return GL10.GL_POINTS;
132 } else if(type.equals("TRIANGLE_STRIP")) {
133 return GL10.GL_TRIANGLE_STRIP;
134 } else if(type.equals("LINE_STRIP")) {
135 return GL10.GL_LINE_STRIP;
137 throw new GdxRuntimeException("Unknown primitive type '" + type + "', should be one of triangle, trianglestrip, line, linestrip, lineloop or point");
141 private VertexAttribute[] parseAttributes (JsonValue attributes) {
142 Array<VertexAttribute> vertexAttributes = new Array<VertexAttribute>();
144 int blendWeightCount = 0;
145 for (JsonValue value = attributes.child(); value != null; value = value.next()) {
146 String attribute = value.asString();
147 String attr = (String)attribute;
148 if(attr.equals("POSITION")) {
149 vertexAttributes.add(VertexAttribute.Position());
150 } else if(attr.equals("NORMAL")) {
151 vertexAttributes.add(VertexAttribute.Normal());
152 } else if(attr.equals("COLOR")) {
153 vertexAttributes.add(VertexAttribute.ColorUnpacked());
154 } else if(attr.equals("COLORPACKED")) {
155 vertexAttributes.add(VertexAttribute.Color());
156 } else if(attr.equals("TANGENT")) {
157 vertexAttributes.add(VertexAttribute.Tangent());
158 } else if(attr.equals("BINORMAL")) {
159 vertexAttributes.add(VertexAttribute.Binormal());
160 } else if(attr.startsWith("TEXCOORD")) {
161 vertexAttributes.add(VertexAttribute.TexCoords(unit++));
162 } else if(attr.startsWith("BLENDWEIGHT")) {
163 vertexAttributes.add(VertexAttribute.BoneWeight(blendWeightCount++));
165 throw new GdxRuntimeException("Unknown vertex attribute '" + attr + "', should be one of position, normal, uv, tangent or binormal");
168 return vertexAttributes.toArray(VertexAttribute.class);
171 private void parseMaterials (ModelData model, JsonValue json, String materialDir) {
172 JsonValue materials = json.get("materials");
173 if(materials == null) {
174 // we should probably create some default material in this case
177 model.materials.ensureCapacity(materials.size());
178 for (JsonValue material = materials.child(); material != null; material = material.next()) {
179 ModelMaterial jsonMaterial = new ModelMaterial();
181 String id = material.getString("id", null);
183 throw new GdxRuntimeException("Material needs an id.");
185 jsonMaterial.id = id;
187 // Read material colors
188 jsonMaterial.diffuse = parseColor(material.get("diffuse"), Color.WHITE);
189 jsonMaterial.ambient = parseColor(material.get("ambient"), Color.BLACK);
190 jsonMaterial.emissive = parseColor(material.get("emissive"), Color.BLACK);
193 jsonMaterial.specular = parseColor(material.get("specular"), Color.BLACK);
195 jsonMaterial.shininess = material.getFloat("shininess", 0.0f);
197 jsonMaterial.opacity = material.getFloat("opacity", 1.0f);
200 JsonValue textures = material.get("textures");
201 if(textures != null){
202 for (JsonValue texture = textures.child(); texture != null; texture = texture.next()) {
203 ModelTexture jsonTexture = new ModelTexture();
205 String textureId = texture.getString("id", null);
206 if(textureId == null)
207 throw new GdxRuntimeException("Texture has no id.");
208 jsonTexture.id = textureId;
210 String fileName = texture.getString("filename", null);
212 throw new GdxRuntimeException("Texture needs filename.");
213 jsonTexture.fileName = materialDir + (materialDir.length() == 0 || materialDir.endsWith("/") ? "" : "/") + fileName;
215 jsonTexture.uvTranslation = readVector2(texture.get("uvTranslation"), 0f, 0f);
216 jsonTexture.uvScaling = readVector2(texture.get("uvScaling"), 1f, 1f);
218 String textureType = texture.getString("type", null);
219 if(textureType == null)
220 throw new GdxRuntimeException("Texture needs type.");
222 jsonTexture.usage = parseTextureUsage(textureType);
224 if(jsonMaterial.textures == null)
225 jsonMaterial.textures = new Array<ModelTexture>();
226 jsonMaterial.textures.add(jsonTexture);
230 model.materials.add(jsonMaterial);
235 private int parseTextureUsage(final String value) {
236 if (value.equalsIgnoreCase("AMBIENT"))
237 return ModelTexture.USAGE_AMBIENT;
238 else if (value.equalsIgnoreCase("BUMP"))
239 return ModelTexture.USAGE_BUMP;
240 else if (value.equalsIgnoreCase("DIFFUSE"))
241 return ModelTexture.USAGE_DIFFUSE;
242 else if (value.equalsIgnoreCase("EMISSIVE"))
243 return ModelTexture.USAGE_EMISSIVE;
244 else if (value.equalsIgnoreCase("NONE"))
245 return ModelTexture.USAGE_NONE;
246 else if (value.equalsIgnoreCase("NORMAL"))
247 return ModelTexture.USAGE_NORMAL;
248 else if (value.equalsIgnoreCase("REFLECTION"))
249 return ModelTexture.USAGE_REFLECTION;
250 else if (value.equalsIgnoreCase("SHININESS"))
251 return ModelTexture.USAGE_SHININESS;
252 else if (value.equalsIgnoreCase("SPECULAR"))
253 return ModelTexture.USAGE_SPECULAR;
254 else if (value.equalsIgnoreCase("TRANSPARENCY"))
255 return ModelTexture.USAGE_TRANSPARENCY;
256 return ModelTexture.USAGE_UNKNOWN;
259 private Color parseColor (JsonValue colorArray, Color defaultColor) {
260 if(colorArray == null) {
263 else if(colorArray.size() == 3)
264 return new Color(colorArray.getFloat(0), colorArray.getFloat(1), colorArray.getFloat(2), 1.0f);
266 throw new GdxRuntimeException("Expected Color values <> than three.");
269 private Vector2 readVector2 (JsonValue vectorArray, float x, float y) {
270 if(vectorArray == null)
271 return new Vector2(x, y);
272 else if(vectorArray.size() == 2)
273 return new Vector2(vectorArray.getFloat(0), vectorArray.getFloat(1));
275 throw new GdxRuntimeException("Expected Vector2 values <> than two.");
278 private Array<ModelNode> parseNodes (ModelData model, JsonValue json) {
279 JsonValue nodes = json.get("nodes");
281 throw new GdxRuntimeException("At least one node is required.");
284 model.nodes.ensureCapacity(nodes.size());
285 for (JsonValue node = nodes.child(); node != null; node = node.next()) {
286 model.nodes.add(parseNodesRecursively(node));
291 private final Quaternion tempQ = new Quaternion();
292 private ModelNode parseNodesRecursively(JsonValue json){
293 ModelNode jsonNode = new ModelNode();
295 String id = json.getString("id", null);
297 throw new GdxRuntimeException("Node id missing.");
300 JsonValue translation = json.get("translation");
301 if (translation != null && translation.size() != 3)
302 throw new GdxRuntimeException("Node translation incomplete");
303 jsonNode.translation = translation == null ? null : new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2));
305 JsonValue rotation = json.get("rotation");
306 if(rotation != null && rotation.size() != 4)
307 throw new GdxRuntimeException("Node rotation incomplete");
308 jsonNode.rotation = rotation == null ? null : new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3));
310 JsonValue scale = json.get("scale");
311 if(scale != null && scale.size() != 3)
312 throw new GdxRuntimeException("Node scale incomplete");
313 jsonNode.scale = scale == null ? null : new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2));
315 String meshId = json.getString("mesh", null);
317 jsonNode.meshId = meshId;
319 JsonValue materials = json.get("parts");
320 if(materials != null){
321 jsonNode.parts = new ModelNodePart[materials.size()];
323 for (JsonValue material = materials.child(); material != null; material = material.next(), i++) {
324 ModelNodePart nodePart = new ModelNodePart();
326 String meshPartId = material.getString("meshpartid", null);
327 String materialId = material.getString("materialid", null);
328 if(meshPartId == null || materialId == null){
329 throw new GdxRuntimeException("Node "+id+" part is missing meshPartId or materialId");
331 nodePart.materialId = materialId;
332 nodePart.meshPartId = meshPartId;
334 JsonValue bones = material.get("bones");
336 nodePart.bones = new ArrayMap<String, Matrix4>(true, bones.size(), String.class, Matrix4.class);
338 for (JsonValue bone = bones.child(); bone != null; bone = bone.next(), j++) {
339 String nodeId = bone.getString("node", null);
341 throw new GdxRuntimeException("Bone node ID missing");
343 Matrix4 transform = new Matrix4();
345 JsonValue val = bone.get("translation");
346 if (val != null && val.size() >= 3)
347 transform.translate(val.getFloat(0), val.getFloat(1), val.getFloat(2));
349 val = bone.get("rotation");
350 if(val != null && val.size() >= 4)
351 transform.rotate(tempQ.set(val.getFloat(0), val.getFloat(1), val.getFloat(2), val.getFloat(3)));
353 val = bone.get("scale");
354 if(val != null && val.size() >= 3)
355 transform.scale(val.getFloat(0), val.getFloat(1), val.getFloat(2));
357 nodePart.bones.put(nodeId, transform);
361 jsonNode.parts[i] = nodePart;
365 JsonValue children = json.get("children");
366 if(children != null){
367 jsonNode.children = new ModelNode[children.size()];
370 for (JsonValue child = children.child(); child != null; child = child.next(), i++) {
371 jsonNode.children[i] = parseNodesRecursively(child);
378 private void parseAnimations (ModelData model, JsonValue json) {
379 JsonValue animations = json.get("animations");
380 if(animations == null)
383 model.animations.ensureCapacity(animations.size());
385 for (JsonValue anim = animations.child(); anim != null; anim = anim.next()) {
386 JsonValue nodes = anim.get("bones");
389 ModelAnimation animation = new ModelAnimation();
390 model.animations.add(animation);
391 animation.nodeAnimations.ensureCapacity(nodes.size());
392 animation.id = anim.getString("id");
393 for (JsonValue node = nodes.child(); node != null; node = node.next()) {
394 JsonValue keyframes = node.get("keyframes");
396 ModelNodeAnimation nodeAnim = new ModelNodeAnimation();
397 animation.nodeAnimations.add(nodeAnim);
398 nodeAnim.nodeId = node.getString("boneId");
399 nodeAnim.keyframes.ensureCapacity(keyframes.size());
401 for (JsonValue keyframe = keyframes.child(); keyframe != null; keyframe = keyframe.next()) {
402 ModelNodeKeyframe kf = new ModelNodeKeyframe();
403 nodeAnim.keyframes.add(kf);
404 kf.keytime = keyframe.getFloat("keytime") / 1000.f;
405 JsonValue translation = keyframe.get("translation");
406 if (translation != null && translation.size() == 3)
407 kf.translation = new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2));
408 JsonValue rotation = keyframe.get("rotation");
409 if (rotation != null && rotation.size() == 4)
410 kf.rotation = new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3));
411 JsonValue scale = keyframe.get("scale");
412 if (scale != null && scale.size() == 3)
413 kf.scale = new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2));