OSDN Git Service

Add some javadocs and some small changes
[mikumikustudio/libgdx-mikumikustudio.git] / gdx / src / com / badlogic / gdx / graphics / g3d / ModelInstance.java
1 package com.badlogic.gdx.graphics.g3d;
2
3 import com.badlogic.gdx.Gdx;
4 import com.badlogic.gdx.graphics.g3d.materials.Material;
5 import com.badlogic.gdx.graphics.g3d.model.Animation;
6 import com.badlogic.gdx.graphics.g3d.model.MeshPart;
7 import com.badlogic.gdx.graphics.g3d.model.NodeAnimation;
8 import com.badlogic.gdx.graphics.g3d.model.NodeKeyframe;
9 import com.badlogic.gdx.graphics.g3d.model.NodePart;
10 import com.badlogic.gdx.graphics.g3d.model.Node;
11 import com.badlogic.gdx.math.Matrix4;
12 import com.badlogic.gdx.math.Vector3;
13 import com.badlogic.gdx.math.collision.BoundingBox;
14 import com.badlogic.gdx.utils.Array;
15 import com.badlogic.gdx.utils.ArrayMap;
16 import com.badlogic.gdx.utils.Disposable;
17 import com.badlogic.gdx.utils.GdxRuntimeException;
18 import com.badlogic.gdx.utils.ObjectMap;
19 import com.badlogic.gdx.utils.Pool;
20
21 /** An instance of a {@link Model}, allows to specify global transform and modify the materials, as it
22  * has a copy of the model's materials. Multiple instances can be created from the same Model, 
23  * all sharing the meshes and textures of the Model. The Model owns the meshes and textures, to 
24  * dispose of these, the Model has to be disposed. Therefor, the Model must outlive all its ModelInstances</p>
25  * 
26  * The ModelInstance creates a full copy of all materials, nodes and animations.
27  * @author badlogic, xoppa */
28 public class ModelInstance implements RenderableProvider {
29         /** 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 **/
30         public final Array<Material> materials = new Array<Material>();
31         /** root nodes of the model **/
32         public final Array<Node> nodes = new Array<Node>();
33         /** animations of the model, modifying node transformations **/
34         public final Array<Animation> animations = new Array<Animation>();
35         /** the {@link Model} this instances derives from **/
36         public final Model model;
37         /** the world transform **/
38         public Matrix4 transform;
39         /** user definable value, which is passed to the shader. */
40         public Object userData;
41         
42         /** Constructs a new ModelInstance with all nodes and materials of the given model.
43          * @param model The {@link Model} to create an instance of. */
44         public ModelInstance(final Model model) {
45                 this(model, (String[])null);
46         }
47         
48         /** @param model The source {@link Model}
49          * @param nodeId The ID of the root {@link Node} of the {@link Model} for the instance to contain
50          * @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
51         public ModelInstance(final Model model, final String nodeId, boolean mergeTransform) {
52                 this(model, null, nodeId, false, false, mergeTransform);
53         }
54         
55         /** @param model The source {@link Model}
56          * @param transform The {@link Matrix4} instance for this ModelInstance to reference or null to create a new matrix. 
57          * @param nodeId The ID of the root {@link Node} of the {@link Model} for the instance to contain
58          * @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
59         public ModelInstance(final Model model, final Matrix4 transform, final String nodeId, boolean mergeTransform) {
60                 this(model, transform, nodeId, false, false, mergeTransform);
61         }
62
63         /** Recursively searches the mode for the specified node.
64          * @param model The source {@link Model}
65          * @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
66          * @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
67          * @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
68         public ModelInstance(final Model model, final String nodeId, boolean parentTransform, boolean mergeTransform) {
69                 this(model, null, nodeId, true, parentTransform, mergeTransform);
70         }
71         
72         /** Recursively searches the mode for the specified node.
73          * @param model The source {@link Model}
74          * @param transform The {@link Matrix4} instance for this ModelInstance to reference or null to create a new matrix. 
75          * @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
76          * @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
77          * @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
78         public ModelInstance(final Model model, final Matrix4 transform, final String nodeId, boolean parentTransform, boolean mergeTransform) {
79                 this(model, transform, nodeId, true, parentTransform, mergeTransform);
80         }
81         
82         /** @param model The source {@link Model}
83          * @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
84          * @param recursive True to recursively search the Model's node tree, false to only search for a root node
85          * @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
86          * @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
87         public ModelInstance(final Model model, final String nodeId, boolean recursive, boolean parentTransform, boolean mergeTransform) {
88                 this(model, null, nodeId, recursive, parentTransform, mergeTransform);
89         }
90         
91         /** @param model The source {@link Model}
92          * @param transform The {@link Matrix4} instance for this ModelInstance to reference or null to create a new matrix. 
93          * @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
94          * @param recursive True to recursively search the Model's node tree, false to only search for a root node
95          * @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
96          * @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
97         public ModelInstance(final Model model, final Matrix4 transform, final String nodeId, boolean recursive, boolean parentTransform, boolean mergeTransform) {
98                 this.model = model;
99                 this.transform = transform == null ? new Matrix4() : transform; 
100                 nodePartBones.clear();
101                 Node copy, node = model.getNode(nodeId, recursive);
102                 this.nodes.add(copy = copyNode(null, node));
103                 if (mergeTransform) {
104                         this.transform.mul(parentTransform ? node.globalTransform : node.localTransform);
105                         copy.translation.set(0,0,0);
106                         copy.rotation.idt();
107                         copy.scale.set(1,1,1);
108                 } else if (parentTransform && copy.parent != null)
109                         this.transform.mul(node.parent.globalTransform);
110                 setBones();
111                 copyAnimations(model.animations);
112                 calculateTransforms();
113         }
114         
115         /** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
116         public ModelInstance(final Model model, final String... rootNodeIds) {
117                 this(model, null, rootNodeIds);
118         }
119         
120         /** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
121         public ModelInstance(final Model model, final Matrix4 transform, final String... rootNodeIds) {
122                 this.model = model;
123                 this.transform = transform == null ? new Matrix4() : transform;
124                 if (rootNodeIds == null)
125                         copyNodes(model.nodes);
126                 else
127                         copyNodes(model.nodes, rootNodeIds);
128                 copyAnimations(model.animations);
129                 calculateTransforms();
130         }
131         
132         /** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
133         public ModelInstance(final Model model, final Array<String> rootNodeIds) {
134                 this(model, null, rootNodeIds);
135         }
136         
137         /** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
138         public ModelInstance(final Model model, final Matrix4 transform, final Array<String> rootNodeIds) {
139                 this.model = model;
140                 this.transform = transform == null ? new Matrix4() : transform;
141                 copyNodes(model.nodes, rootNodeIds);
142                 copyAnimations(model.animations);
143                 calculateTransforms();
144         }
145         
146         /** Constructs a new ModelInstance at the specified position. */
147         public ModelInstance(final Model model, Vector3 position) {
148                 this(model);
149                 this.transform.setToTranslation(position);
150         }
151         
152         /** Constructs a new ModelInstance at the specified position. */
153         public ModelInstance(final Model model, float x, float y, float z) {
154                 this(model);
155                 this.transform.setToTranslation(x, y, z);
156         }
157         
158         /** Constructs a new ModelInstance with the specified transform. */
159         public ModelInstance(final Model model, Matrix4 transform) {
160                 this(model, transform, (String[])null);
161         }
162         
163         /** Constructs a new ModelInstance which is an copy of the specified ModelInstance. */
164         public ModelInstance(ModelInstance copyFrom) {
165                 this(copyFrom, copyFrom.transform.cpy());
166         }
167         
168         /** Constructs a new ModelInstance which is an copy of the specified ModelInstance. */
169         public ModelInstance(ModelInstance copyFrom, final Matrix4 transform) {
170                 this.model = copyFrom.model;
171                 this.transform = transform == null ? new Matrix4() : transform;
172                 copyNodes(copyFrom.nodes);
173                 copyAnimations(copyFrom.animations);
174                 calculateTransforms();
175         }
176         
177         /** @return A newly created ModelInstance which is a copy of this ModelInstance */
178         public ModelInstance copy() {
179                 return new ModelInstance(this);
180         }
181
182         private ObjectMap<NodePart, ArrayMap<Node, Matrix4>> nodePartBones = new ObjectMap<NodePart, ArrayMap<Node, Matrix4>>();
183         private void copyNodes (Array<Node> nodes) {
184                 nodePartBones.clear();
185                 for(int i = 0, n = nodes.size; i<n; ++i) {
186                         final Node node = nodes.get(i);
187                         this.nodes.add(copyNode(null, node));
188                 }
189                 setBones();
190         }
191         
192         private void copyNodes (Array<Node> nodes, final String... nodeIds) {
193                 nodePartBones.clear();
194                 for(int i = 0, n = nodes.size; i<n; ++i) {
195                         final Node node = nodes.get(i);
196                         for (final String nodeId : nodeIds) {
197                                 if (nodeId.equals(node.id)) {
198                                         this.nodes.add(copyNode(null, node));
199                                         break;
200                                 }
201                         }
202                 }
203                 setBones();
204         }
205         
206         private void copyNodes (Array<Node> nodes, final Array<String> nodeIds) {
207                 nodePartBones.clear();
208                 for(int i = 0, n = nodes.size; i<n; ++i) {
209                         final Node node = nodes.get(i);
210                         for (final String nodeId : nodeIds) {
211                                 if (nodeId.equals(node.id)) {
212                                         this.nodes.add(copyNode(null, node));
213                                         break;
214                                 }
215                         }
216                 }
217                 setBones();
218         }
219         
220         private void setBones() {
221                 for (ObjectMap.Entry<NodePart,ArrayMap<Node, Matrix4>> e : nodePartBones.entries()) {
222                         if (e.key.invBoneBindTransforms == null)
223                                 e.key.invBoneBindTransforms = new ArrayMap<Node, Matrix4>(true, e.value.size, Node.class, Matrix4.class);
224                         e.key.invBoneBindTransforms.clear();
225                         
226                         for (final ObjectMap.Entry<Node, Matrix4> b : e.value.entries())
227                                 e.key.invBoneBindTransforms.put(getNode(b.key.id), b.value); // Share the inv bind matrix with the model
228                         
229                         e.key.bones = new Matrix4[e.value.size];
230                         for (int i = 0; i < e.key.bones.length; i++)
231                                 e.key.bones[i] = new Matrix4();
232                 }
233         }
234         
235         private Node copyNode(Node parent, Node node) {
236                 Node copy = new Node();
237                 copy.id = node.id;
238                 //copy.boneId = node.boneId;
239                 copy.parent = parent;
240                 copy.translation.set(node.translation);
241                 copy.rotation.set(node.rotation);
242                 copy.scale.set(node.scale);
243                 copy.localTransform.set(node.localTransform);
244                 copy.globalTransform.set(node.globalTransform);
245                 for(NodePart nodePart: node.parts) {
246                         copy.parts.add(copyNodePart(nodePart));
247                 }
248                 for(Node child: node.children) {
249                         copy.children.add(copyNode(copy, child));
250                 }
251                 return copy;
252         }
253         
254         private NodePart copyNodePart (NodePart nodePart) {
255                 NodePart copy = new NodePart();
256                 copy.meshPart = new MeshPart();
257                 copy.meshPart.id = nodePart.meshPart.id;
258                 copy.meshPart.indexOffset = nodePart.meshPart.indexOffset;
259                 copy.meshPart.numVertices = nodePart.meshPart.numVertices;
260                 copy.meshPart.primitiveType = nodePart.meshPart.primitiveType;
261                 copy.meshPart.mesh = nodePart.meshPart.mesh;
262                 
263                 if (nodePart.invBoneBindTransforms != null)
264                         nodePartBones.put(copy, nodePart.invBoneBindTransforms);
265                 
266                 final int index = materials.indexOf(nodePart.material, false);
267                 if (index < 0)
268                         materials.add(copy.material = nodePart.material.copy());
269                 else
270                         copy.material = materials.get(index);
271                 
272                 return copy;
273         }
274         
275         private void copyAnimations (final Iterable<Animation> source) {
276                 for (final Animation anim : source) {
277                         Animation animation = new Animation();
278                         animation.id = anim.id;
279                         animation.duration = anim.duration;
280                         for (final NodeAnimation nanim : anim.nodeAnimations) {
281                                 final Node node = getNode(nanim.node.id);
282                                 if (node == null)
283                                         continue;
284                                 NodeAnimation nodeAnim = new NodeAnimation();
285                                 nodeAnim.node = node;
286                                 for (final NodeKeyframe kf : nanim.keyframes) {
287                                         NodeKeyframe keyframe = new NodeKeyframe();
288                                         keyframe.keytime = kf.keytime;
289                                         keyframe.rotation.set(kf.rotation);
290                                         keyframe.scale.set(kf.scale);
291                                         keyframe.translation.set(kf.translation);
292                                         nodeAnim.keyframes.add(keyframe);
293                                 }
294                                 if (nodeAnim.keyframes.size > 0)
295                                         animation.nodeAnimations.add(nodeAnim);
296                         }
297                         if (animation.nodeAnimations.size > 0)
298                                 animations.add(animation);
299                 }
300         }
301         
302         
303         /**
304          * Traverses the Node hierarchy and collects {@link Renderable} instances for every
305          * node with a graphical representation. Renderables are obtained from the provided
306          * pool. The resulting array can be rendered via a {@link ModelBatch}.
307          * 
308          * @param renderables the output array
309          * @param pool the pool to obtain Renderables from
310          */
311         public void getRenderables(Array<Renderable> renderables, Pool<Renderable> pool) {
312                 for(Node node: nodes) {
313                         getRenderables(node, renderables, pool);
314                 }
315         }
316
317         /** @return The renderable of the first node's first part. */
318         public Renderable getRenderable(final Renderable out) {
319                 return getRenderable(out, nodes.get(0));
320         }
321         
322         /** @return The renderable of the node's first part. */
323         public Renderable getRenderable(final Renderable out, final Node node) {
324                 return getRenderable(out, node, node.parts.get(0));
325         }
326         
327         public Renderable getRenderable(final Renderable out, final Node node, final NodePart nodePart) {
328                 nodePart.setRenderable(out);
329                 if (nodePart.bones == null && transform != null)
330                         out.worldTransform.set(transform).mul(node.globalTransform);
331                 else if (transform != null)
332                         out.worldTransform.set(transform);
333                 else
334                         out.worldTransform.idt();
335                 out.userData = userData;
336                 return out;
337         }
338         
339         protected void getRenderables(Node node, Array<Renderable> renderables, Pool<Renderable> pool) {
340                 if(node.parts.size > 0) {
341                         for(NodePart nodePart: node.parts) {
342                                 renderables.add(getRenderable(pool.obtain(), node, nodePart));
343                         }
344                 }
345                 
346                 for(Node child: node.children) {
347                         getRenderables(child, renderables, pool);
348                 }
349         }
350         
351         /** Calculates the local and world transform of all {@link Node} instances in this model, recursively.
352          * First each {@link Node#localTransform} transform is calculated based on the translation, rotation and
353          * scale of each Node. Then each {@link Node#calculateWorldTransform()}
354          * is calculated, based on the parent's world transform and the local transform of each Node.
355          * Finally, the animation bone matrices are updated accordingly.</p>
356          * 
357          * This method can be used to recalculate all transforms if any of the Node's local properties (translation, rotation, scale)
358          * was modified.
359          */
360         public void calculateTransforms() {
361                 final int n = nodes.size;
362                 for(int i = 0; i < n; i++) {
363                         nodes.get(i).calculateTransforms(true);
364                 }
365                 for(int i = 0; i < n; i++) {
366                         nodes.get(i).calculateBoneTransforms(true);
367                 }
368         }
369         
370         /** Calculate the bounding box of this model instance.
371          * This is a potential slow operation, it is advised to cache the result.
372          * @param out the {@link BoundingBox} that will be set with the bounds.
373          * @return the out parameter for chaining */
374         public BoundingBox calculateBoundingBox(final BoundingBox out) {
375                 out.inf();
376                 return extendBoundingBox(out);
377         }
378         
379         /** Extends the bounding box with the bounds of this model instance.
380          * This is a potential slow operation, it is advised to cache the result.
381          * @param out the {@link BoundingBox} that will be extended with the bounds.
382          * @return the out parameter for chaining */
383         public BoundingBox extendBoundingBox(final BoundingBox out) {
384                 final int n = nodes.size;
385                 for(int i = 0; i < n; i++)
386                         nodes.get(i).extendBoundingBox(out);
387                 return out;
388         }
389
390         /** @param id The ID of the animation to fetch (case sensitive).
391          * @return The {@link Animation} with the specified id, or null if not available. */
392         public Animation getAnimation(final String id) {
393                 return getAnimation(id, true);
394         }
395         
396         /** @param id The ID of the animation to fetch.
397          * @param ignoreCase whether to use case sensitivity when comparing the animation id.
398          * @return The {@link Animation} with the specified id, or null if not available. */
399         public Animation getAnimation(final String id, boolean ignoreCase) {
400                 final int n = animations.size;
401                 Animation animation;
402                 if (ignoreCase) {
403                         for (int i = 0; i < n; i++)
404                                 if ((animation = animations.get(i)).id.equalsIgnoreCase(id))
405                                         return animation;
406                 } else {
407                         for (int i = 0; i < n; i++)
408                                 if ((animation = animations.get(i)).id.equals(id))
409                                         return animation;
410                 }
411                 return null;
412         }
413         
414         /** @param id The ID of the material to fetch.
415          * @return The {@link Material} with the specified id, or null if not available. */
416         public Material getMaterial(final String id) {
417                 return getMaterial(id, true);
418         }
419         
420         /** @param id The ID of the material to fetch.
421          * @param ignoreCase whether to use case sensitivity when comparing the material id.
422          * @return The {@link Material} with the specified id, or null if not available. */
423         public Material getMaterial(final String id, boolean ignoreCase) {
424                 final int n = materials.size;
425                 Material material;
426                 if (ignoreCase) {
427                         for (int i = 0; i < n; i++)
428                                 if ((material = materials.get(i)).id.equalsIgnoreCase(id))
429                                         return material;
430                 } else {
431                         for (int i = 0; i < n; i++)
432                                 if ((material = materials.get(i)).id.equals(id))
433                                         return material;
434                 }
435                 return null;
436         }
437         
438         /** @param id The ID of the node to fetch.
439          * @return The {@link Node} with the specified id, or null if not found. */
440         public Node getNode(final String id) {
441                 return getNode(id, true);
442         }
443         
444         /** @param id The ID of the node to fetch.
445          * @param recursive false to fetch a root node only, true to search the entire node tree for the specified node.
446          * @return The {@link Node} with the specified id, or null if not found. */
447         public Node getNode(final String id, boolean recursive) {
448                 return getNode(id, recursive, false);
449         }
450         
451         /** @param id The ID of the node to fetch.
452          * @param recursive false to fetch a root node only, true to search the entire node tree for the specified node.
453          * @param ignoreCase whether to use case sensitivity when comparing the node id.
454          * @return The {@link Node} with the specified id, or null if not found. */
455         public Node getNode(final String id, boolean recursive, boolean ignoreCase) {
456                 return Node.getNode(nodes, id, recursive, ignoreCase);
457         }
458 }