• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.graphics.g3d.model;
18 
19 import com.badlogic.gdx.graphics.g3d.Material;
20 import com.badlogic.gdx.graphics.g3d.Model;
21 import com.badlogic.gdx.math.Matrix4;
22 import com.badlogic.gdx.math.Quaternion;
23 import com.badlogic.gdx.math.Vector3;
24 import com.badlogic.gdx.math.collision.BoundingBox;
25 import com.badlogic.gdx.utils.Array;
26 import com.badlogic.gdx.utils.GdxRuntimeException;
27 
28 /** A node is part of a hierarchy of Nodes in a {@link Model}. A Node encodes a transform relative to its parents. A Node can have
29  * child nodes. Optionally a node can specify a {@link MeshPart} and a {@link Material} to be applied to the mesh part.
30  * @author badlogic */
31 public class Node {
32 	/** the id, may be null, FIXME is this unique? **/
33 	public String id;
34 	/** Whether this node should inherit the transformation of its parent node, defaults to true. When this flag is false the value
35 	 * of {@link #globalTransform} will be the same as the value of {@link #localTransform} causing the transform to be independent
36 	 * of its parent transform. */
37 	public boolean inheritTransform = true;
38 	/** Whether this node is currently being animated, if so the translation, rotation and scale values are not used. */
39 	public boolean isAnimated;
40 	/** the translation, relative to the parent, not modified by animations **/
41 	public final Vector3 translation = new Vector3();
42 	/** the rotation, relative to the parent, not modified by animations **/
43 	public final Quaternion rotation = new Quaternion(0, 0, 0, 1);
44 	/** the scale, relative to the parent, not modified by animations **/
45 	public final Vector3 scale = new Vector3(1, 1, 1);
46 	/** the local transform, based on translation/rotation/scale ({@link #calculateLocalTransform()}) or any applied animation **/
47 	public final Matrix4 localTransform = new Matrix4();
48 	/** the global transform, product of local transform and transform of the parent node, calculated via
49 	 * {@link #calculateWorldTransform()} **/
50 	public final Matrix4 globalTransform = new Matrix4();
51 
52 	public Array<NodePart> parts = new Array<NodePart>(2);
53 
54 	protected Node parent;
55 	private final Array<Node> children = new Array<Node>(2);
56 
57 	/** Calculates the local transform based on the translation, scale and rotation
58 	 * @return the local transform */
calculateLocalTransform()59 	public Matrix4 calculateLocalTransform () {
60 		if (!isAnimated) localTransform.set(translation, rotation, scale);
61 		return localTransform;
62 	}
63 
64 	/** Calculates the world transform; the product of local transform and the parent's world transform.
65 	 * @return the world transform */
calculateWorldTransform()66 	public Matrix4 calculateWorldTransform () {
67 		if (inheritTransform && parent != null)
68 			globalTransform.set(parent.globalTransform).mul(localTransform);
69 		else
70 			globalTransform.set(localTransform);
71 		return globalTransform;
72 	}
73 
74 	/** Calculates the local and world transform of this node and optionally all its children.
75 	 *
76 	 * @param recursive whether to calculate the local/world transforms for children. */
calculateTransforms(boolean recursive)77 	public void calculateTransforms (boolean recursive) {
78 		calculateLocalTransform();
79 		calculateWorldTransform();
80 
81 		if (recursive) {
82 			for (Node child : children) {
83 				child.calculateTransforms(true);
84 			}
85 		}
86 	}
87 
calculateBoneTransforms(boolean recursive)88 	public void calculateBoneTransforms (boolean recursive) {
89 		for (final NodePart part : parts) {
90 			if (part.invBoneBindTransforms == null || part.bones == null || part.invBoneBindTransforms.size != part.bones.length)
91 				continue;
92 			final int n = part.invBoneBindTransforms.size;
93 			for (int i = 0; i < n; i++)
94 				part.bones[i].set(part.invBoneBindTransforms.keys[i].globalTransform).mul(part.invBoneBindTransforms.values[i]);
95 		}
96 		if (recursive) {
97 			for (Node child : children) {
98 				child.calculateBoneTransforms(true);
99 			}
100 		}
101 	}
102 
103 	/** Calculate the bounding box of this Node. This is a potential slow operation, it is advised to cache the result. */
calculateBoundingBox(final BoundingBox out)104 	public BoundingBox calculateBoundingBox (final BoundingBox out) {
105 		out.inf();
106 		return extendBoundingBox(out);
107 	}
108 
109 	/** Calculate the bounding box of this Node. This is a potential slow operation, it is advised to cache the result. */
calculateBoundingBox(final BoundingBox out, boolean transform)110 	public BoundingBox calculateBoundingBox (final BoundingBox out, boolean transform) {
111 		out.inf();
112 		return extendBoundingBox(out, transform);
113 	}
114 
115 	/** Extends the bounding box with the bounds of this Node. This is a potential slow operation, it is advised to cache the
116 	 * result. */
extendBoundingBox(final BoundingBox out)117 	public BoundingBox extendBoundingBox (final BoundingBox out) {
118 		return extendBoundingBox(out, true);
119 	}
120 
121 	/** Extends the bounding box with the bounds of this Node. This is a potential slow operation, it is advised to cache the
122 	 * result. */
extendBoundingBox(final BoundingBox out, boolean transform)123 	public BoundingBox extendBoundingBox (final BoundingBox out, boolean transform) {
124 		final int partCount = parts.size;
125 		for (int i = 0; i < partCount; i++) {
126 			final NodePart part = parts.get(i);
127 			if (part.enabled) {
128 				final MeshPart meshPart = part.meshPart;
129 				if (transform)
130 					meshPart.mesh.extendBoundingBox(out, meshPart.offset, meshPart.size, globalTransform);
131 				else
132 					meshPart.mesh.extendBoundingBox(out, meshPart.offset, meshPart.size);
133 			}
134 		}
135 		final int childCount = children.size;
136 		for (int i = 0; i < childCount; i++)
137 			children.get(i).extendBoundingBox(out);
138 		return out;
139 	}
140 
141 	/** Adds this node as child to specified parent Node, synonym for: <code>parent.addChild(this)</code>
142 	 * @param parent The Node to attach this Node to. */
attachTo(T parent)143 	public <T extends Node> void attachTo (T parent) {
144 		parent.addChild(this);
145 	}
146 
147 	/** Removes this node from its current parent, if any. Short for: <code>this.getParent().removeChild(this)</code> */
detach()148 	public void detach () {
149 		if (parent != null) {
150 			parent.removeChild(this);
151 			parent = null;
152 		}
153 	}
154 
155 	/** @return whether this Node has one or more children (true) or not (false) */
hasChildren()156 	public boolean hasChildren () {
157 		return children != null && children.size > 0;
158 	}
159 
160 	/** @return The number of child nodes that this Node current contains.
161 	 * @see #getChild(int) */
getChildCount()162 	public int getChildCount () {
163 		return children.size;
164 	}
165 
166 	/** @param index The zero-based index of the child node to get, must be: 0 <= index < {@link #getChildCount()}.
167 	 * @return The child node at the specified index */
getChild(final int index)168 	public Node getChild (final int index) {
169 		return children.get(index);
170 	}
171 
172 	/** @param recursive false to fetch a root child only, true to search the entire node tree for the specified node.
173 	 * @return The node with the specified id, or null if not found. */
getChild(final String id, boolean recursive, boolean ignoreCase)174 	public Node getChild (final String id, boolean recursive, boolean ignoreCase) {
175 		return getNode(children, id, recursive, ignoreCase);
176 	}
177 
178 	/** Adds the specified node as the currently last child of this node. If the node is already a child of another node, then it is
179 	 * removed from its current parent.
180 	 * @param child The Node to add as child of this Node
181 	 * @return the zero-based index of the child */
addChild(final T child)182 	public <T extends Node> int addChild (final T child) {
183 		return insertChild(-1, child);
184 	}
185 
186 	/** Adds the specified nodes as the currently last child of this node. If the node is already a child of another node, then it
187 	 * is removed from its current parent.
188 	 * @param nodes The Node to add as child of this Node
189 	 * @return the zero-based index of the first added child */
addChildren(final Iterable<T> nodes)190 	public <T extends Node> int addChildren (final Iterable<T> nodes) {
191 		return insertChildren(-1, nodes);
192 	}
193 
194 	/** Insert the specified node as child of this node at the specified index. If the node is already a child of another node, then
195 	 * it is removed from its current parent. If the specified index is less than zero or equal or greater than
196 	 * {@link #getChildCount()} then the Node is added as the currently last child.
197 	 * @param index The zero-based index at which to add the child
198 	 * @param child The Node to add as child of this Node
199 	 * @return the zero-based index of the child */
insertChild(int index, final T child)200 	public <T extends Node> int insertChild (int index, final T child) {
201 		for (Node p = this; p != null; p = p.getParent()) {
202 			if (p == child) throw new GdxRuntimeException("Cannot add a parent as a child");
203 		}
204 		Node p = child.getParent();
205 		if (p != null && !p.removeChild(child)) throw new GdxRuntimeException("Could not remove child from its current parent");
206 		if (index < 0 || index >= children.size) {
207 			index = children.size;
208 			children.add(child);
209 		} else
210 			children.insert(index, child);
211 		child.parent = this;
212 		return index;
213 	}
214 
215 	/** Insert the specified nodes as children of this node at the specified index. If the node is already a child of another node,
216 	 * then it is removed from its current parent. If the specified index is less than zero or equal or greater than
217 	 * {@link #getChildCount()} then the Node is added as the currently last child.
218 	 * @param index The zero-based index at which to add the child
219 	 * @param nodes The nodes to add as child of this Node
220 	 * @return the zero-based index of the first inserted child */
insertChildren(int index, final Iterable<T> nodes)221 	public <T extends Node> int insertChildren (int index, final Iterable<T> nodes) {
222 		if (index < 0 || index > children.size) index = children.size;
223 		int i = index;
224 		for (T child : nodes)
225 			insertChild(i++, child);
226 		return index;
227 	}
228 
229 	/** Removes the specified node as child of this node. On success, the child node will be not attached to any parent node (its
230 	 * {@link #getParent()} method will return null). If the specified node currently isn't a child of this node then the removal
231 	 * is considered to be unsuccessful and the method will return false.
232 	 * @param child The child node to remove.
233 	 * @return Whether the removal was successful. */
removeChild(final T child)234 	public <T extends Node> boolean removeChild (final T child) {
235 		if (!children.removeValue(child, true)) return false;
236 		child.parent = null;
237 		return true;
238 	}
239 
240 	/** @return An {@link Iterable} to all child nodes that this node contains. */
getChildren()241 	public Iterable<Node> getChildren () {
242 		return children;
243 	}
244 
245 	/** @return The parent node that holds this node as child node, may be null. */
getParent()246 	public Node getParent () {
247 		return parent;
248 	}
249 
250 	/** @return Whether (true) is this Node is a child node of another node or not (false). */
hasParent()251 	public boolean hasParent () {
252 		return parent != null;
253 	}
254 
255 	/** Creates a nested copy of this Node, any child nodes are copied using this method as well. The {@link #parts} are copied
256 	 * using the {@link NodePart#copy()} method. Note that that method copies the material and nodes (bones) by reference. If you
257 	 * intend to use the copy in a different node tree (e.g. a different Model or ModelInstance) then you will need to update these
258 	 * references afterwards.
259 	 *
260 	 * Override this method in your custom Node class to instantiate that class, in that case you should override the
261 	 * {@link #set(Node)} method as well. */
copy()262 	public Node copy () {
263 		return new Node().set(this);
264 	}
265 
266 	/** Creates a nested copy of this Node, any child nodes are copied using the {@link #copy()} method. This will detach this node
267 	 * from its parent, but does not attach it to the parent of node being copied. The {@link #parts} are copied using the
268 	 * {@link NodePart#copy()} method. Note that that method copies the material and nodes (bones) by reference. If you intend to
269 	 * use this node in a different node tree (e.g. a different Model or ModelInstance) then you will need to update these
270 	 * references afterwards.
271 	 *
272 	 * Override this method in your custom Node class to copy any additional fields you've added.
273 	 * @return This Node for chaining */
set(Node other)274 	protected Node set (Node other) {
275 		detach();
276 		id = other.id;
277 		isAnimated = other.isAnimated;
278 		inheritTransform = other.inheritTransform;
279 		translation.set(other.translation);
280 		rotation.set(other.rotation);
281 		scale.set(other.scale);
282 		localTransform.set(other.localTransform);
283 		globalTransform.set(other.globalTransform);
284 		parts.clear();
285 		for (NodePart nodePart : other.parts) {
286 			parts.add(nodePart.copy());
287 		}
288 		children.clear();
289 		for (Node child : other.getChildren()) {
290 			addChild(child.copy());
291 		}
292 		return this;
293 	}
294 
295 	/** Helper method to recursive fetch a node from an array
296 	 * @param recursive false to fetch a root node only, true to search the entire node tree for the specified node.
297 	 * @return The node with the specified id, or null if not found. */
getNode(final Array<Node> nodes, final String id, boolean recursive, boolean ignoreCase)298 	public static Node getNode (final Array<Node> nodes, final String id, boolean recursive, boolean ignoreCase) {
299 		final int n = nodes.size;
300 		Node node;
301 		if (ignoreCase) {
302 			for (int i = 0; i < n; i++)
303 				if ((node = nodes.get(i)).id.equalsIgnoreCase(id)) return node;
304 		} else {
305 			for (int i = 0; i < n; i++)
306 				if ((node = nodes.get(i)).id.equals(id)) return node;
307 		}
308 		if (recursive) {
309 			for (int i = 0; i < n; i++)
310 				if ((node = getNode(nodes.get(i).children, id, true, ignoreCase)) != null) return node;
311 		}
312 		return null;
313 	}
314 }
315