package loon.opengl.d3d;
import loon.LRelease;
import loon.LSystem;
import loon.LTexture;
import loon.geom.BoundingBox;
import loon.geom.Matrix4;
import loon.opengl.GL20;
import loon.opengl.Mesh;
import loon.opengl.VertexAttributes;
import loon.opengl.d3d.materials.BlendingAttribute;
import loon.opengl.d3d.materials.ColorAttribute;
import loon.opengl.d3d.materials.FloatAttribute;
import loon.opengl.d3d.materials.Material;
import loon.opengl.d3d.materials.TextureAttribute;
import loon.opengl.d3d.materials.TextureDescriptor;
import loon.opengl.d3d.materials.TextureProvider;
import loon.opengl.d3d.models.Animation;
import loon.opengl.d3d.models.MeshPart;
import loon.opengl.d3d.models.ModelAnimation;
import loon.opengl.d3d.models.ModelData;
import loon.opengl.d3d.models.ModelMaterial;
import loon.opengl.d3d.models.ModelMesh;
import loon.opengl.d3d.models.ModelMeshPart;
import loon.opengl.d3d.models.ModelNode;
import loon.opengl.d3d.models.ModelNodeAnimation;
import loon.opengl.d3d.models.ModelNodeKeyframe;
import loon.opengl.d3d.models.ModelNodePart;
import loon.opengl.d3d.models.ModelTexture;
import loon.opengl.d3d.models.Node;
import loon.opengl.d3d.models.NodeAnimation;
import loon.opengl.d3d.models.NodeKeyframe;
import loon.opengl.d3d.models.NodePart;
import loon.utils.ListMap;
import loon.utils.ObjectMap;
import loon.utils.TArray;
public class Model implements LRelease {
public final TArray<Material> materials = new TArray<Material>();
public final TArray<Node> nodes = new TArray<Node>();
public final TArray<Animation> animations = new TArray<Animation>();
public final TArray<Mesh> meshes = new TArray<Mesh>();
public final TArray<MeshPart> meshParts = new TArray<MeshPart>();
protected final TArray<LRelease> disposables = new TArray<LRelease>();
public Model() {}
public Model(ModelData modelData) {
this(modelData, new TextureProvider.FileTextureProvider());
}
public Model(ModelData modelData, TextureProvider textureProvider) {
load(modelData, textureProvider);
}
private void load(ModelData modelData, TextureProvider textureProvider) {
loadMeshes(modelData.meshes);
loadMaterials(modelData.materials, textureProvider);
loadNodes(modelData.nodes);
loadAnimations(modelData.animations);
calculateTransforms();
}
private void loadAnimations (Iterable<ModelAnimation> modelAnimations) {
for (final ModelAnimation anim : modelAnimations) {
Animation animation = new Animation();
animation.id = anim.id;
for (ModelNodeAnimation nanim : anim.nodeAnimations) {
final Node node = getNode(nanim.nodeId);
if (node == null){
continue;
}
NodeAnimation nodeAnim = new NodeAnimation();
nodeAnim.node = node;
for (ModelNodeKeyframe kf : nanim.keyframes) {
if (kf.keytime > animation.duration){
animation.duration = kf.keytime;
}
NodeKeyframe keyframe = new NodeKeyframe();
keyframe.keytime = kf.keytime;
keyframe.rotation.set(kf.rotation == null ? node.rotation : kf.rotation);
keyframe.scale.set(kf.scale == null ? node.scale : kf.scale);
keyframe.translation.set(kf.translation == null ? node.translation : kf.translation);
nodeAnim.keyframes.add(keyframe);
}
if (nodeAnim.keyframes.size > 0)
animation.nodeAnimations.add(nodeAnim);
}
if (animation.nodeAnimations.size > 0)
animations.add(animation);
}
}
private ObjectMap<NodePart, ListMap<String, Matrix4>> nodePartBones = new ObjectMap<NodePart, ListMap<String, Matrix4>>();
private void loadNodes (Iterable<ModelNode> modelNodes) {
nodePartBones.clear();
for(ModelNode node: modelNodes) {
nodes.add(loadNode(null, node));
}
for (ObjectMap.Entry<NodePart,ListMap<String, Matrix4>> e : nodePartBones.entries()) {
if (e.key.invBoneBindTransforms == null)
e.key.invBoneBindTransforms = new ListMap<Node, Matrix4>();
e.key.invBoneBindTransforms.clear();
for (int i=0;i<e.value.size;i++)
e.key.invBoneBindTransforms.put(getNode(e.value.keys[i]), new Matrix4(e.value.values[i]).inv());
}
}
private Node loadNode (Node parent, ModelNode modelNode) {
Node node = new Node();
node.id = modelNode.id;
node.parent = parent;
if (modelNode.translation != null)
node.translation.set(modelNode.translation);
if (modelNode.rotation != null)
node.rotation.set(modelNode.rotation);
if (modelNode.scale != null)
node.scale.set(modelNode.scale);
if (modelNode.parts != null) {
for(ModelNodePart modelNodePart: modelNode.parts) {
MeshPart meshPart = null;
Material meshMaterial = null;
if(modelNodePart.meshPartId != null) {
for(MeshPart part: meshParts) {
if(modelNodePart.meshPartId.equals(part.id)) {
meshPart = part;
break;
}
}
}
if(modelNodePart.materialId != null) {
for(Material material: materials) {
if(modelNodePart.materialId.equals(material.id)) {
meshMaterial = material;
break;
}
}
}
if (meshPart == null || meshMaterial == null)
throw new RuntimeException("Invalid node: "+node.id);
if(meshPart != null && meshMaterial != null) {
NodePart nodePart = new NodePart();
nodePart.meshPart = meshPart;
nodePart.material = meshMaterial;
node.parts.add(nodePart);
if (modelNodePart.bones != null)
nodePartBones.put(nodePart, modelNodePart.bones);
}
}
}
if(modelNode.children != null) {
for(ModelNode child: modelNode.children) {
node.children.add(loadNode(node, child));
}
}
return node;
}
private void loadMeshes (Iterable<ModelMesh> meshes) {
for(ModelMesh mesh: meshes) {
convertMesh(mesh);
}
}
private void convertMesh (ModelMesh modelMesh) {
int numIndices = 0;
for(ModelMeshPart part: modelMesh.parts) {
numIndices += part.indices.length;
}
VertexAttributes attributes = new VertexAttributes(modelMesh.attributes);
int numVertices = modelMesh.vertices.length / (attributes.vertexSize / 4);
Mesh mesh = new Mesh(true, numVertices, numIndices, attributes);
meshes.add(mesh);
disposables.add(mesh);
LSystem.base().support().copy(modelMesh.vertices, mesh.getVerticesBuffer(), 0,modelMesh.vertices.length);
int offset = 0;
mesh.getIndicesBuffer().clear();
for(ModelMeshPart part: modelMesh.parts) {
MeshPart meshPart = new MeshPart();
meshPart.id = part.id;
meshPart.primitiveType = part.primitiveType;
meshPart.indexOffset = offset;
meshPart.numVertices = part.indices.length;
meshPart.mesh = mesh;
mesh.getIndicesBuffer().put(part.indices);
offset += meshPart.numVertices;
meshParts.add(meshPart);
}
mesh.getIndicesBuffer().position(0);
}
private void loadMaterials (Iterable<ModelMaterial> modelMaterials, TextureProvider textureProvider) {
for(ModelMaterial mtl: modelMaterials) {
this.materials.add(convertMaterial(mtl, textureProvider));
}
}
private Material convertMaterial(ModelMaterial mtl, TextureProvider textureProvider) {
Material result = new Material();
result.id = mtl.id;
if (mtl.ambient != null)
result.set(new ColorAttribute(ColorAttribute.Ambient, mtl.ambient));
if (mtl.diffuse != null)
result.set(new ColorAttribute(ColorAttribute.Diffuse, mtl.diffuse));
if (mtl.specular != null)
result.set(new ColorAttribute(ColorAttribute.Specular, mtl.specular));
if (mtl.emissive != null)
result.set(new ColorAttribute(ColorAttribute.Emissive, mtl.emissive));
if (mtl.shininess > 0f)
result.set(new FloatAttribute(FloatAttribute.Shininess, mtl.shininess));
if (mtl.opacity != 1.f)
result.set(new BlendingAttribute(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, mtl.opacity));
ObjectMap<String, LTexture> textures = new ObjectMap<String, LTexture>();
if(mtl.textures != null) {
for(ModelTexture tex: mtl.textures) {
LTexture texture;
if(textures.containsKey(tex.fileName)) {
texture = textures.get(tex.fileName);
} else {
texture = textureProvider.load(tex.fileName);
textures.put(tex.fileName, texture);
disposables.add(texture);
}
TextureDescriptor descriptor = new TextureDescriptor(texture);
descriptor.minFilter = GL20.GL_LINEAR;
descriptor.magFilter = GL20.GL_LINEAR;
switch (tex.usage) {
case ModelTexture.USAGE_DIFFUSE:
result.set(new TextureAttribute(TextureAttribute.Diffuse, descriptor));
break;
case ModelTexture.USAGE_SPECULAR:
result.set(new TextureAttribute(TextureAttribute.Specular, descriptor));
break;
case ModelTexture.USAGE_BUMP:
result.set(new TextureAttribute(TextureAttribute.Bump, descriptor));
break;
case ModelTexture.USAGE_NORMAL:
result.set(new TextureAttribute(TextureAttribute.Normal, descriptor));
break;
}
}
}
return result;
}
public void manageDisposable(LRelease disposable) {
if (!disposables.contains(disposable, true))
disposables.add(disposable);
}
public Iterable<LRelease> getManagedDisposables() {
return disposables;
}
@Override
public void close () {
for(LRelease disposable: disposables) {
disposable.close();
}
}
public void calculateTransforms() {
final int n = nodes.size;
for(int i = 0; i < n; i++) {
nodes.get(i).calculateTransforms(true);
}
for(int i = 0; i < n; i++) {
nodes.get(i).calculateBoneTransforms(true);
}
}
public BoundingBox calculateBoundingBox(final BoundingBox out) {
out.inf();
return extendBoundingBox(out);
}
public BoundingBox extendBoundingBox(final BoundingBox out) {
final int n = nodes.size;
for(int i = 0; i < n; i++)
nodes.get(i).extendBoundingBox(out);
return out;
}
public Animation getAnimation(final String id) {
return getAnimation(id, true);
}
public Animation getAnimation(final String id, boolean ignoreCase) {
final int n = animations.size;
Animation animation;
if (ignoreCase) {
for (int i = 0; i < n; i++)
if ((animation = animations.get(i)).id.equalsIgnoreCase(id))
return animation;
} else {
for (int i = 0; i < n; i++)
if ((animation = animations.get(i)).id.equals(id))
return animation;
}
return null;
}
public Material getMaterial(final String id) {
return getMaterial(id, true);
}
public Material getMaterial(final String id, boolean ignoreCase) {
final int n = materials.size;
Material material;
if (ignoreCase) {
for (int i = 0; i < n; i++)
if ((material = materials.get(i)).id.equalsIgnoreCase(id))
return material;
} else {
for (int i = 0; i < n; i++)
if ((material = materials.get(i)).id.equals(id))
return material;
}
return null;
}
public Node getNode(final String id) {
return getNode(id, true);
}
public Node getNode(final String id, boolean recursive) {
return getNode(id, recursive, false);
}
public Node getNode(final String id, boolean recursive, boolean ignoreCase) {
return Node.getNode(nodes, id, recursive, ignoreCase);
}
}