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