package org.jrenner.fps.headless; import com.badlogic.gdx.assets.loaders.FileHandleResolver; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.VertexAttribute; import com.badlogic.gdx.graphics.g3d.model.data.ModelAnimation; import com.badlogic.gdx.graphics.g3d.model.data.ModelData; import com.badlogic.gdx.graphics.g3d.model.data.ModelMaterial; import com.badlogic.gdx.graphics.g3d.model.data.ModelMesh; import com.badlogic.gdx.graphics.g3d.model.data.ModelMeshPart; import com.badlogic.gdx.graphics.g3d.model.data.ModelNode; import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeAnimation; import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeKeyframe; import com.badlogic.gdx.graphics.g3d.model.data.ModelNodePart; import com.badlogic.gdx.graphics.g3d.model.data.ModelTexture; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ArrayMap; import com.badlogic.gdx.utils.BaseJsonReader; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.JsonValue; public class HeadlessG3dModelLoader extends HeadlessModelLoader<HeadlessModelLoader.HeadlessModelParameters> { public static final short VERSION_HI = 0; public static final short VERSION_LO = 1; protected final BaseJsonReader reader; public HeadlessG3dModelLoader(final BaseJsonReader reader) { this(reader, null); } public HeadlessG3dModelLoader(BaseJsonReader reader, FileHandleResolver resolver) { super(resolver); this.reader = reader; } @Override public ModelData loadModelData (FileHandle fileHandle, HeadlessModelParameters parameters) { return parseModel(fileHandle); } public ModelData parseModel (FileHandle handle) { JsonValue json = reader.parse(handle); ModelData model = new ModelData(); JsonValue version = json.require("version"); model.version[0] = version.getShort(0); model.version[1] = version.getShort(1); if (model.version[0] != VERSION_HI || model.version[1] != VERSION_LO) throw new GdxRuntimeException("Model version not supported"); model.id = json.getString("id", ""); parseMeshes(model, json); //parseMaterials(model, json, handle.parent().path()); parseNodes(model, json); parseAnimations(model, json); return model; } private void parseMeshes (ModelData model, JsonValue json) { JsonValue meshes = json.require("meshes"); model.meshes.ensureCapacity(meshes.size); for (JsonValue mesh = meshes.child; mesh != null; mesh = mesh.next) { ModelMesh jsonMesh = new ModelMesh(); String id = mesh.getString("id", ""); jsonMesh.id = id; JsonValue attributes = mesh.require("attributes"); jsonMesh.attributes = parseAttributes(attributes); jsonMesh.vertices = mesh.require("vertices").asFloatArray(); JsonValue meshParts = mesh.require("parts"); Array<ModelMeshPart> parts = new Array<ModelMeshPart>(); for (JsonValue meshPart = meshParts.child; meshPart != null; meshPart = meshPart.next) { ModelMeshPart jsonPart = new ModelMeshPart(); String partId = meshPart.getString("id", null); if (id == null) { throw new GdxRuntimeException("Not id given for mesh part"); } for (ModelMeshPart other : parts) { if (other.id.equals(partId)) { throw new GdxRuntimeException("Mesh part with id '" + partId + "' already in defined"); } } jsonPart.id = partId; String type = meshPart.getString("type", null); if (type == null) { throw new GdxRuntimeException("No primitive type given for mesh part '" + partId + "'"); } jsonPart.primitiveType = parseType(type); jsonPart.indices = meshPart.require("indices").asShortArray(); parts.add(jsonPart); } jsonMesh.parts = parts.toArray(ModelMeshPart.class); model.meshes.add(jsonMesh); } } private int parseType (String type) { if (type.equals("TRIANGLES")) { return GL20.GL_TRIANGLES; } else if (type.equals("LINES")) { return GL20.GL_LINES; } else if (type.equals("POINTS")) { return GL20.GL_POINTS; } else if (type.equals("TRIANGLE_STRIP")) { return GL20.GL_TRIANGLE_STRIP; } else if (type.equals("LINE_STRIP")) { return GL20.GL_LINE_STRIP; } else { throw new GdxRuntimeException("Unknown primitive type '" + type + "', should be one of triangle, trianglestrip, line, linestrip, lineloop or point"); } } private VertexAttribute[] parseAttributes (JsonValue attributes) { Array<VertexAttribute> vertexAttributes = new Array<VertexAttribute>(); int unit = 0; int blendWeightCount = 0; for (JsonValue value = attributes.child; value != null; value = value.next) { String attribute = value.asString(); String attr = (String)attribute; if (attr.equals("POSITION")) { vertexAttributes.add(VertexAttribute.Position()); } else if (attr.equals("NORMAL")) { vertexAttributes.add(VertexAttribute.Normal()); } else if (attr.equals("COLOR")) { vertexAttributes.add(VertexAttribute.ColorUnpacked()); } else if (attr.equals("COLORPACKED")) { vertexAttributes.add(VertexAttribute.Color()); } else if (attr.equals("TANGENT")) { vertexAttributes.add(VertexAttribute.Tangent()); } else if (attr.equals("BINORMAL")) { vertexAttributes.add(VertexAttribute.Binormal()); } else if (attr.startsWith("TEXCOORD")) { vertexAttributes.add(VertexAttribute.TexCoords(unit++)); } else if (attr.startsWith("BLENDWEIGHT")) { vertexAttributes.add(VertexAttribute.BoneWeight(blendWeightCount++)); } else { throw new GdxRuntimeException("Unknown vertex attribute '" + attr + "', should be one of position, normal, uv, tangent or binormal"); } } return vertexAttributes.toArray(VertexAttribute.class); } private Array<ModelNode> parseNodes (ModelData model, JsonValue json) { JsonValue nodes = json.get("nodes"); if (nodes == null) { throw new GdxRuntimeException("At least one node is required."); } model.nodes.ensureCapacity(nodes.size); for (JsonValue node = nodes.child; node != null; node = node.next) { model.nodes.add(parseNodesRecursively(node)); } return model.nodes; } private final Quaternion tempQ = new Quaternion(); private ModelNode parseNodesRecursively (JsonValue json) { ModelNode jsonNode = new ModelNode(); String id = json.getString("id", null); if (id == null) throw new GdxRuntimeException("Node id missing."); jsonNode.id = id; JsonValue translation = json.get("translation"); if (translation != null && translation.size != 3) throw new GdxRuntimeException("Node translation incomplete"); jsonNode.translation = translation == null ? null : new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2)); JsonValue rotation = json.get("rotation"); if (rotation != null && rotation.size != 4) throw new GdxRuntimeException("Node rotation incomplete"); jsonNode.rotation = rotation == null ? null : new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3)); JsonValue scale = json.get("scale"); if (scale != null && scale.size != 3) throw new GdxRuntimeException("Node scale incomplete"); jsonNode.scale = scale == null ? null : new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2)); String meshId = json.getString("mesh", null); if (meshId != null) jsonNode.meshId = meshId; JsonValue materials = json.get("parts"); if (materials != null) { jsonNode.parts = new ModelNodePart[materials.size]; int i = 0; for (JsonValue material = materials.child; material != null; material = material.next, i++) { ModelNodePart nodePart = new ModelNodePart(); String meshPartId = material.getString("meshpartid", null); String materialId = material.getString("materialid", null); if (meshPartId == null || materialId == null) { throw new GdxRuntimeException("Node " + id + " part is missing meshPartId or materialId"); } nodePart.materialId = materialId; nodePart.meshPartId = meshPartId; JsonValue bones = material.get("bones"); if (bones != null) { nodePart.bones = new ArrayMap<String, Matrix4>(true, bones.size, String.class, Matrix4.class); int j = 0; for (JsonValue bone = bones.child; bone != null; bone = bone.next, j++) { String nodeId = bone.getString("node", null); if (nodeId == null) throw new GdxRuntimeException("Bone node ID missing"); Matrix4 transform = new Matrix4(); JsonValue val = bone.get("translation"); if (val != null && val.size >= 3) transform.translate(val.getFloat(0), val.getFloat(1), val.getFloat(2)); val = bone.get("rotation"); if (val != null && val.size >= 4) transform.rotate(tempQ.set(val.getFloat(0), val.getFloat(1), val.getFloat(2), val.getFloat(3))); val = bone.get("scale"); if (val != null && val.size >= 3) transform.scale(val.getFloat(0), val.getFloat(1), val.getFloat(2)); nodePart.bones.put(nodeId, transform); } } jsonNode.parts[i] = nodePart; } } JsonValue children = json.get("children"); if (children != null) { jsonNode.children = new ModelNode[children.size]; int i = 0; for (JsonValue child = children.child; child != null; child = child.next, i++) { jsonNode.children[i] = parseNodesRecursively(child); } } return jsonNode; } private void parseAnimations (ModelData model, JsonValue json) { JsonValue animations = json.get("animations"); if (animations == null) return; model.animations.ensureCapacity(animations.size); for (JsonValue anim = animations.child; anim != null; anim = anim.next) { JsonValue nodes = anim.get("bones"); if (nodes == null) continue; ModelAnimation animation = new ModelAnimation(); model.animations.add(animation); animation.nodeAnimations.ensureCapacity(nodes.size); animation.id = anim.getString("id"); for (JsonValue node = nodes.child; node != null; node = node.next) { JsonValue keyframes = node.get("keyframes"); ModelNodeAnimation nodeAnim = new ModelNodeAnimation(); animation.nodeAnimations.add(nodeAnim); nodeAnim.nodeId = node.getString("boneId"); nodeAnim.keyframes.ensureCapacity(keyframes.size); for (JsonValue keyframe = keyframes.child; keyframe != null; keyframe = keyframe.next) { ModelNodeKeyframe kf = new ModelNodeKeyframe(); nodeAnim.keyframes.add(kf); kf.keytime = keyframe.getFloat("keytime") / 1000.f; JsonValue translation = keyframe.get("translation"); if (translation != null && translation.size == 3) kf.translation = new Vector3(translation.getFloat(0), translation.getFloat(1), translation.getFloat(2)); JsonValue rotation = keyframe.get("rotation"); if (rotation != null && rotation.size == 4) kf.rotation = new Quaternion(rotation.getFloat(0), rotation.getFloat(1), rotation.getFloat(2), rotation.getFloat(3)); JsonValue scale = keyframe.get("scale"); if (scale != null && scale.size == 3) kf.scale = new Vector3(scale.getFloat(0), scale.getFloat(1), scale.getFloat(2)); } } } } }