OSDN Git Service

Merge pull request #589 from jrenner/graphics-array-refactor
[mikumikustudio/libgdx-mikumikustudio.git] / gdx / src / com / badlogic / gdx / graphics / g3d / Model.java
1 package com.badlogic.gdx.graphics.g3d;
2
3 import com.badlogic.gdx.Gdx;
4 import com.badlogic.gdx.assets.loaders.ModelLoader;
5 import com.badlogic.gdx.files.FileHandle;
6 import com.badlogic.gdx.graphics.GL10;
7 import com.badlogic.gdx.graphics.GL20;
8 import com.badlogic.gdx.graphics.Mesh;
9 import com.badlogic.gdx.graphics.Texture;
10 import com.badlogic.gdx.graphics.VertexAttribute;
11 import com.badlogic.gdx.graphics.VertexAttributes;
12 import com.badlogic.gdx.graphics.g3d.materials.BlendingAttribute;
13 import com.badlogic.gdx.graphics.g3d.materials.ColorAttribute;
14 import com.badlogic.gdx.graphics.g3d.materials.FloatAttribute;
15 import com.badlogic.gdx.graphics.g3d.materials.Material;
16 import com.badlogic.gdx.graphics.g3d.materials.TextureAttribute;
17 import com.badlogic.gdx.graphics.g3d.model.Animation;
18 import com.badlogic.gdx.graphics.g3d.model.MeshPart;
19 import com.badlogic.gdx.graphics.g3d.model.NodeAnimation;
20 import com.badlogic.gdx.graphics.g3d.model.NodeKeyframe;
21 import com.badlogic.gdx.graphics.g3d.model.NodePart;
22 import com.badlogic.gdx.graphics.g3d.model.Node;
23 import com.badlogic.gdx.graphics.g3d.model.data.ModelAnimation;
24 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeAnimation;
25 import com.badlogic.gdx.graphics.g3d.model.data.ModelNodeKeyframe;
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.ModelNodePart;
31 import com.badlogic.gdx.graphics.g3d.model.data.ModelNode;
32 import com.badlogic.gdx.graphics.g3d.model.data.ModelTexture;
33 import com.badlogic.gdx.graphics.g3d.utils.TextureDescriptor;
34 import com.badlogic.gdx.graphics.g3d.utils.TextureProvider;
35 import com.badlogic.gdx.graphics.g3d.utils.TextureProvider.FileTextureProvider;
36 import com.badlogic.gdx.math.Matrix4;
37 import com.badlogic.gdx.math.collision.BoundingBox;
38 import com.badlogic.gdx.utils.Array;
39 import com.badlogic.gdx.utils.ArrayMap;
40 import com.badlogic.gdx.utils.BufferUtils;
41 import com.badlogic.gdx.utils.Disposable;
42 import com.badlogic.gdx.utils.GdxRuntimeException;
43 import com.badlogic.gdx.utils.ObjectMap;
44 import com.badlogic.gdx.utils.Pool;
45
46 /**
47  * A model represents a 3D assets. It stores a hierarchy of nodes. A node has a transform and optionally
48  * a graphical part in form of a {@link MeshPart} and {@link Material}. Mesh parts reference subsets of
49  * vertices in one of the meshes of the model. Animations can be applied to nodes, to modify their
50  * transform (translation, rotation, scale) over time.</p>
51  *
52  * A model can be rendered by creating a {@link ModelInstance} from it. That instance has an additional
53  * transform to position the model in the world, and allows modification of materials and nodes without
54  * destroying the original model. The original model is the owner of any meshes and textures, all instances
55  * created from the model share these resources. Disposing the model will automatically make all instances
56  * invalid!</p>
57  * 
58  * A model is created from {@link ModelData}, which in turn is loaded by a {@link ModelLoader}.
59  *   
60  * @author badlogic, xoppa */
61 public class Model implements Disposable {
62         /** the materials of the model, used by nodes that have a graphical representation FIXME not sure if superfluous, allows modification of materials without having to traverse the nodes **/
63         public final Array<Material> materials = new Array<Material>();
64         /** root nodes of the model **/
65         public final Array<Node> nodes = new Array<Node>();
66         /** animations of the model, modifying node transformations **/
67         public final Array<Animation> animations = new Array<Animation>();
68         /** the meshes of the model **/
69         public final Array<Mesh> meshes = new Array<Mesh>();
70         /** parts of meshes, used by nodes that have a graphical representation FIXME not sure if superfluous, stored in Nodes as well, could be useful to create bullet meshes **/
71         public final Array<MeshPart> meshParts = new Array<MeshPart>();
72         /** Array of disposable resources like textures or meshes the Model is responsible for disposing **/
73         protected final Array<Disposable> disposables = new Array<Disposable>();
74         
75         /** Constructs an empty model. Manual created models do not manage their resources by default. 
76          * Use {@link #manageDisposable(Disposable)} to add resources to be managed by this model. */
77         public Model() {}
78         
79         /** Constructs a new Model based on the {@link ModelData}. Texture files
80          * will be loaded from the internal file storage via an {@link FileTextureProvider}.
81          * @param modelData the {@link ModelData} got from e.g. {@link ModelLoader} */
82         public Model(ModelData modelData) {
83                 this(modelData, new FileTextureProvider());
84         }
85
86         /** Constructs a new Model based on the {@link ModelData}.
87          * @param modelData the {@link ModelData} got from e.g. {@link ModelLoader}
88          * @param textureProvider the {@link TextureProvider} to use for loading the textures */
89         public Model(ModelData modelData, TextureProvider textureProvider) {
90                 load(modelData, textureProvider);
91         }
92         
93         private void load(ModelData modelData, TextureProvider textureProvider) {
94                 loadMeshes(modelData.meshes);
95                 loadMaterials(modelData.materials, textureProvider);
96                 loadNodes(modelData.nodes);
97                 loadAnimations(modelData.animations);
98                 calculateTransforms();
99         }
100         
101         private void loadAnimations (Iterable<ModelAnimation> modelAnimations) {
102                 for (final ModelAnimation anim : modelAnimations) {
103                         Animation animation = new Animation();
104                         animation.id = anim.id;
105                         for (ModelNodeAnimation nanim : anim.nodeAnimations) {
106                                 final Node node = getNode(nanim.nodeId);
107                                 if (node == null)
108                                         continue;
109                                 NodeAnimation nodeAnim = new NodeAnimation();
110                                 nodeAnim.node = node;
111                                 for (ModelNodeKeyframe kf : nanim.keyframes) {
112                                         if (kf.keytime > animation.duration)
113                                                 animation.duration = kf.keytime;
114                                         NodeKeyframe keyframe = new NodeKeyframe();
115                                         keyframe.keytime = kf.keytime;
116                                         keyframe.rotation.set(kf.rotation == null ? node.rotation : kf.rotation);
117                                         keyframe.scale.set(kf.scale == null ? node.scale : kf.scale);
118                                         keyframe.translation.set(kf.translation == null ? node.translation : kf.translation);                                   
119                                         nodeAnim.keyframes.add(keyframe);
120                                 }
121                                 if (nodeAnim.keyframes.size > 0)
122                                         animation.nodeAnimations.add(nodeAnim);
123                         }
124                         if (animation.nodeAnimations.size > 0)
125                                 animations.add(animation);
126                 }
127         }
128
129         private ObjectMap<NodePart, ArrayMap<String, Matrix4>> nodePartBones = new ObjectMap<NodePart, ArrayMap<String, Matrix4>>(); 
130         private void loadNodes (Iterable<ModelNode> modelNodes) {
131                 nodePartBones.clear();
132                 for(ModelNode node: modelNodes) {
133                         nodes.add(loadNode(null, node));
134                 }
135                 for (ObjectMap.Entry<NodePart,ArrayMap<String, Matrix4>> e : nodePartBones.entries()) {
136                         if (e.key.invBoneBindTransforms == null)
137                                 e.key.invBoneBindTransforms = new ArrayMap<Node, Matrix4>(Node.class, Matrix4.class);
138                         e.key.invBoneBindTransforms.clear();
139                         for (ObjectMap.Entry<String, Matrix4> b : e.value.entries())
140                                 e.key.invBoneBindTransforms.put(getNode(b.key), new Matrix4(b.value).inv());
141                 }
142         }
143
144         private Node loadNode (Node parent, ModelNode modelNode) {
145                 Node node = new Node();
146                 node.id = modelNode.id;
147                 node.parent = parent;
148                 
149                 if (modelNode.translation != null)
150                         node.translation.set(modelNode.translation);
151                 if (modelNode.rotation != null)
152                         node.rotation.set(modelNode.rotation);
153                 if (modelNode.scale != null)
154                         node.scale.set(modelNode.scale);
155                 // FIXME create temporary maps for faster lookup?
156                 if (modelNode.parts != null) {
157                         for(ModelNodePart modelNodePart: modelNode.parts) {
158                                 MeshPart meshPart = null;
159                                 Material meshMaterial = null;
160                                 
161                                 if(modelNodePart.meshPartId != null) {
162                                         for(MeshPart part: meshParts) {
163                                                 if(modelNodePart.meshPartId.equals(part.id)) {
164                                                         meshPart = part;
165                                                         break;
166                                                 }
167                                         }
168                                 }
169                                 
170                                 if(modelNodePart.materialId != null) {
171                                         for(Material material: materials) {
172                                                 if(modelNodePart.materialId.equals(material.id)) {
173                                                         meshMaterial = material;
174                                                         break;
175                                                 }
176                                         }
177                                 }
178                                 
179                                 if (meshPart == null || meshMaterial == null)
180                                         throw new GdxRuntimeException("Invalid node: "+node.id);
181                                 
182                                 if(meshPart != null && meshMaterial != null) {
183                                         NodePart nodePart = new NodePart();
184                                         nodePart.meshPart = meshPart;
185                                         nodePart.material = meshMaterial;
186                                         node.parts.add(nodePart);
187                                         if (modelNodePart.bones != null)
188                                                 nodePartBones.put(nodePart, modelNodePart.bones);
189                                 }
190                         }
191                 }
192                 
193                 if(modelNode.children != null) {
194                         for(ModelNode child: modelNode.children) {
195                                 node.children.add(loadNode(node, child));
196                         }
197                 }
198                 
199                 return node;
200         }
201
202         private void loadMeshes (Iterable<ModelMesh> meshes) {
203                 for(ModelMesh mesh: meshes) {
204                         convertMesh(mesh);
205                 }
206         }
207
208         private void convertMesh (ModelMesh modelMesh) {
209                 int numIndices = 0;
210                 for(ModelMeshPart part: modelMesh.parts) {
211                         numIndices += part.indices.length;
212                 }
213                 VertexAttributes attributes = new VertexAttributes(modelMesh.attributes);
214                 int numVertices = modelMesh.vertices.length / (attributes.vertexSize / 4);
215                 
216                 Mesh mesh = new Mesh(true, numVertices, numIndices, attributes);
217                 meshes.add(mesh);
218                 disposables.add(mesh);
219                 
220                 BufferUtils.copy(modelMesh.vertices, mesh.getVerticesBuffer(), modelMesh.vertices.length, 0);
221                 int offset = 0;
222                 mesh.getIndicesBuffer().clear();
223                 for(ModelMeshPart part: modelMesh.parts) {
224                         MeshPart meshPart = new MeshPart();
225                         meshPart.id = part.id;
226                         meshPart.primitiveType = part.primitiveType;
227                         meshPart.indexOffset = offset;
228                         meshPart.numVertices = part.indices.length;
229                         meshPart.mesh = mesh;
230                         mesh.getIndicesBuffer().put(part.indices);
231                         offset += meshPart.numVertices;
232                         meshParts.add(meshPart);
233                 }
234                 mesh.getIndicesBuffer().position(0);
235         }
236
237         private void loadMaterials (Iterable<ModelMaterial> modelMaterials, TextureProvider textureProvider) {
238                 for(ModelMaterial mtl: modelMaterials) {
239                         this.materials.add(convertMaterial(mtl, textureProvider));
240                 }
241         }
242         
243         private Material convertMaterial(ModelMaterial mtl, TextureProvider textureProvider) {
244                 Material result = new Material();
245                 result.id = mtl.id;
246                 if (mtl.ambient != null)
247                         result.set(new ColorAttribute(ColorAttribute.Ambient, mtl.ambient));
248                 if (mtl.diffuse != null)
249                         result.set(new ColorAttribute(ColorAttribute.Diffuse, mtl.diffuse));
250                 if (mtl.specular != null)
251                         result.set(new ColorAttribute(ColorAttribute.Specular, mtl.specular));
252                 if (mtl.emissive != null)
253                         result.set(new ColorAttribute(ColorAttribute.Emissive, mtl.emissive));
254                 if (mtl.reflection != null)
255                         result.set(new ColorAttribute(ColorAttribute.Reflection, mtl.reflection));
256                 if (mtl.shininess > 0f)
257                         result.set(new FloatAttribute(FloatAttribute.Shininess, mtl.shininess));
258                 if (mtl.opacity != 1.f)
259                         result.set(new BlendingAttribute(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA, mtl.opacity));
260                 
261                 ObjectMap<String, Texture> textures = new ObjectMap<String, Texture>();
262                 
263                 // FIXME mipmapping totally ignored, filters totally ignored, uvScaling/uvTranslation totally ignored
264                 if(mtl.textures != null) {
265                         for(ModelTexture tex: mtl.textures) {
266                                 Texture texture;
267                                 if(textures.containsKey(tex.fileName)) {
268                                         texture = textures.get(tex.fileName);
269                                 } else {
270                                         texture = textureProvider.load(tex.fileName);
271                                         textures.put(tex.fileName, texture);
272                                         disposables.add(texture);
273                                 }
274                                 
275                                 TextureDescriptor descriptor = new TextureDescriptor(texture);
276                                 descriptor.minFilter = Texture.TextureFilter.Linear;
277                                 descriptor.magFilter = Texture.TextureFilter.Linear;
278                                 descriptor.uWrap = Texture.TextureWrap.Repeat;
279                                 descriptor.vWrap = Texture.TextureWrap.Repeat;
280                                 switch (tex.usage) {
281                                 case ModelTexture.USAGE_DIFFUSE:
282                                         result.set(new TextureAttribute(TextureAttribute.Diffuse, descriptor));
283                                         break;
284                                 case ModelTexture.USAGE_SPECULAR:
285                                         result.set(new TextureAttribute(TextureAttribute.Specular, descriptor));
286                                         break;
287                                 case ModelTexture.USAGE_BUMP:
288                                         result.set(new TextureAttribute(TextureAttribute.Bump, descriptor));
289                                         break;
290                                 case ModelTexture.USAGE_NORMAL:
291                                         result.set(new TextureAttribute(TextureAttribute.Normal, descriptor));
292                                         break;                                  
293                                 }
294                         }
295                 }
296                 
297                 return result;
298         }
299         
300         /** Adds a {@link Disposable} to be managed and disposed by this Model. Can
301          * be used to keep track of manually loaded textures for {@link ModelInstance}.
302          * @param disposable the Disposable */
303         public void manageDisposable(Disposable disposable) {
304                 if (!disposables.contains(disposable, true))
305                         disposables.add(disposable);
306         }
307         
308         /** @return the {@link Disposable} objects that will be disposed when the {@link #dispose()} method is called. */
309         public Iterable<Disposable> getManagedDisposables() {
310                 return disposables;
311         }
312
313         @Override
314         public void dispose () {
315                 for(Disposable disposable: disposables) {
316                         disposable.dispose();
317                 }
318         }
319         
320         /** Calculates the local and world transform of all {@link Node} instances in this model, recursively.
321          * First each {@link Node#localTransform} transform is calculated based on the translation, rotation and
322          * scale of each Node. Then each {@link Node#calculateWorldTransform()}
323          * is calculated, based on the parent's world transform and the local transform of each Node.
324          * Finally, the animation bone matrices are updated accordingly.</p>
325          * 
326          * This method can be used to recalculate all transforms if any of the Node's local properties (translation, rotation, scale)
327          * was modified.
328          */
329         public void calculateTransforms() {
330                 final int n = nodes.size;
331                 for(int i = 0; i < n; i++) {
332                         nodes.get(i).calculateTransforms(true);
333                 }
334                 for(int i = 0; i < n; i++) {
335                         nodes.get(i).calculateBoneTransforms(true);
336                 }
337         }
338         
339         /** Calculate the bounding box of this model instance.
340          * This is a potential slow operation, it is advised to cache the result.
341          * @param out the {@link BoundingBox} that will be set with the bounds.
342          * @return the out parameter for chaining */
343         public BoundingBox calculateBoundingBox(final BoundingBox out) {
344                 out.inf();
345                 return extendBoundingBox(out);
346         }
347         
348         /** Extends the bounding box with the bounds of this model instance.
349          * This is a potential slow operation, it is advised to cache the result.
350          * @param out the {@link BoundingBox} that will be extended with the bounds.
351          * @return the out parameter for chaining */
352         public BoundingBox extendBoundingBox(final BoundingBox out) {
353                 final int n = nodes.size;
354                 for(int i = 0; i < n; i++)
355                         nodes.get(i).extendBoundingBox(out);
356                 return out;
357         }
358
359         /** @param id The ID of the animation to fetch (case sensitive).
360          * @return The {@link Animation} with the specified id, or null if not available. */
361         public Animation getAnimation(final String id) {
362                 return getAnimation(id, true);
363         }
364         
365         /** @param id The ID of the animation to fetch.
366          * @param ignoreCase whether to use case sensitivity when comparing the animation id.
367          * @return The {@link Animation} with the specified id, or null if not available. */
368         public Animation getAnimation(final String id, boolean ignoreCase) {
369                 final int n = animations.size;
370                 Animation animation;
371                 if (ignoreCase) {
372                         for (int i = 0; i < n; i++)
373                                 if ((animation = animations.get(i)).id.equalsIgnoreCase(id))
374                                         return animation;
375                 } else {
376                         for (int i = 0; i < n; i++)
377                                 if ((animation = animations.get(i)).id.equals(id))
378                                         return animation;
379                 }
380                 return null;
381         }
382         
383         /** @param id The ID of the material to fetch.
384          * @return The {@link Material} with the specified id, or null if not available. */
385         public Material getMaterial(final String id) {
386                 return getMaterial(id, true);
387         }
388         
389         /** @param id The ID of the material to fetch.
390          * @param ignoreCase whether to use case sensitivity when comparing the material id.
391          * @return The {@link Material} with the specified id, or null if not available. */
392         public Material getMaterial(final String id, boolean ignoreCase) {
393                 final int n = materials.size;
394                 Material material;
395                 if (ignoreCase) {
396                         for (int i = 0; i < n; i++)
397                                 if ((material = materials.get(i)).id.equalsIgnoreCase(id))
398                                         return material;
399                 } else {
400                         for (int i = 0; i < n; i++)
401                                 if ((material = materials.get(i)).id.equals(id))
402                                         return material;
403                 }
404                 return null;
405         }
406         
407         /** @param id The ID of the node to fetch.
408          * @return The {@link Node} with the specified id, or null if not found. */
409         public Node getNode(final String id) {
410                 return getNode(id, true);
411         }
412         
413         /** @param id The ID of the node to fetch.
414          * @param recursive false to fetch a root node only, true to search the entire node tree for the specified node.
415          * @return The {@link Node} with the specified id, or null if not found. */
416         public Node getNode(final String id, boolean recursive) {
417                 return getNode(id, recursive, false);
418         }
419         
420         /** @param id The ID of the node to fetch.
421          * @param recursive false to fetch a root node only, true to search the entire node tree for the specified node.
422          * @param ignoreCase whether to use case sensitivity when comparing the node id.
423          * @return The {@link Node} with the specified id, or null if not found. */
424         public Node getNode(final String id, boolean recursive, boolean ignoreCase) {
425                 return Node.getNode(nodes, id, recursive, ignoreCase);
426         }
427 }