1 package com.badlogic.gdx.graphics.g3d;
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;
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>
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;
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);
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);
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);
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);
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);
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);
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) {
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);
107 copy.scale.set(1,1,1);
108 } else if (parentTransform && copy.parent != null)
109 this.transform.mul(node.parent.globalTransform);
111 copyAnimations(model.animations);
112 calculateTransforms();
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);
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) {
123 this.transform = transform == null ? new Matrix4() : transform;
124 if (rootNodeIds == null)
125 copyNodes(model.nodes);
127 copyNodes(model.nodes, rootNodeIds);
128 copyAnimations(model.animations);
129 calculateTransforms();
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);
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) {
140 this.transform = transform == null ? new Matrix4() : transform;
141 copyNodes(model.nodes, rootNodeIds);
142 copyAnimations(model.animations);
143 calculateTransforms();
146 /** Constructs a new ModelInstance at the specified position. */
147 public ModelInstance(final Model model, Vector3 position) {
149 this.transform.setToTranslation(position);
152 /** Constructs a new ModelInstance at the specified position. */
153 public ModelInstance(final Model model, float x, float y, float z) {
155 this.transform.setToTranslation(x, y, z);
158 /** Constructs a new ModelInstance with the specified transform. */
159 public ModelInstance(final Model model, Matrix4 transform) {
160 this(model, transform, (String[])null);
163 /** Constructs a new ModelInstance which is an copy of the specified ModelInstance. */
164 public ModelInstance(ModelInstance copyFrom) {
165 this(copyFrom, copyFrom.transform.cpy());
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();
177 /** @return A newly created ModelInstance which is a copy of this ModelInstance */
178 public ModelInstance copy() {
179 return new ModelInstance(this);
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));
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));
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));
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();
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
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();
235 private Node copyNode(Node parent, Node node) {
236 Node copy = new Node();
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));
248 for(Node child: node.children) {
249 copy.children.add(copyNode(copy, child));
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;
263 if (nodePart.invBoneBindTransforms != null)
264 nodePartBones.put(copy, nodePart.invBoneBindTransforms);
266 final int index = materials.indexOf(nodePart.material, false);
268 materials.add(copy.material = nodePart.material.copy());
270 copy.material = materials.get(index);
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);
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);
294 if (nodeAnim.keyframes.size > 0)
295 animation.nodeAnimations.add(nodeAnim);
297 if (animation.nodeAnimations.size > 0)
298 animations.add(animation);
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}.
308 * @param renderables the output array
309 * @param pool the pool to obtain Renderables from
311 public void getRenderables(Array<Renderable> renderables, Pool<Renderable> pool) {
312 for(Node node: nodes) {
313 getRenderables(node, renderables, pool);
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));
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));
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);
334 out.worldTransform.idt();
335 out.userData = userData;
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));
346 for(Node child: node.children) {
347 getRenderables(child, renderables, pool);
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>
357 * This method can be used to recalculate all transforms if any of the Node's local properties (translation, rotation, scale)
360 public void calculateTransforms() {
361 final int n = nodes.size;
362 for(int i = 0; i < n; i++) {
363 nodes.get(i).calculateTransforms(true);
365 for(int i = 0; i < n; i++) {
366 nodes.get(i).calculateBoneTransforms(true);
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) {
376 return extendBoundingBox(out);
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);
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);
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;
403 for (int i = 0; i < n; i++)
404 if ((animation = animations.get(i)).id.equalsIgnoreCase(id))
407 for (int i = 0; i < n; i++)
408 if ((animation = animations.get(i)).id.equals(id))
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);
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;
427 for (int i = 0; i < n; i++)
428 if ((material = materials.get(i)).id.equalsIgnoreCase(id))
431 for (int i = 0; i < n; i++)
432 if ((material = materials.get(i)).id.equals(id))
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);
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);
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);