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));
}
}
}
}
}