All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.badlogic.gdx.graphics.g3d.model.Node Maven / Gradle / Ivy

There is a newer version: 1.12.1
Show newest version
/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.graphics.g3d.model;

import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;

/** 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
 * child nodes. Optionally a node can specify a {@link MeshPart} and a {@link Material} to be applied to the mesh part.
 * @author badlogic */
public class Node {
	/** the id, may be null, FIXME is this unique? **/
	public String id;
	/** Whether this node should inherit the transformation of its parent node, defaults to true. When this flag is false the value
	 * of {@link #globalTransform} will be the same as the value of {@link #localTransform} causing the transform to be independent
	 * of its parent transform. */
	public boolean inheritTransform = true;
	/** Whether this node is currently being animated, if so the translation, rotation and scale values are not used. */
	public boolean isAnimated;
	/** the translation, relative to the parent, not modified by animations **/
	public final Vector3 translation = new Vector3();
	/** the rotation, relative to the parent, not modified by animations **/
	public final Quaternion rotation = new Quaternion(0, 0, 0, 1);
	/** the scale, relative to the parent, not modified by animations **/
	public final Vector3 scale = new Vector3(1, 1, 1);
	/** the local transform, based on translation/rotation/scale ({@link #calculateLocalTransform()}) or any applied animation **/
	public final Matrix4 localTransform = new Matrix4();
	/** the global transform, product of local transform and transform of the parent node, calculated via
	 * {@link #calculateWorldTransform()} **/
	public final Matrix4 globalTransform = new Matrix4();

	public Array parts = new Array(2);

	protected Node parent;
	private final Array children = new Array(2);

	/** Calculates the local transform based on the translation, scale and rotation
	 * @return the local transform */
	public Matrix4 calculateLocalTransform () {
		if (!isAnimated) localTransform.set(translation, rotation, scale);
		return localTransform;
	}

	/** Calculates the world transform; the product of local transform and the parent's world transform.
	 * @return the world transform */
	public Matrix4 calculateWorldTransform () {
		if (inheritTransform && parent != null)
			globalTransform.set(parent.globalTransform).mul(localTransform);
		else
			globalTransform.set(localTransform);
		return globalTransform;
	}

	/** Calculates the local and world transform of this node and optionally all its children.
	 * 
	 * @param recursive whether to calculate the local/world transforms for children. */
	public void calculateTransforms (boolean recursive) {
		calculateLocalTransform();
		calculateWorldTransform();

		if (recursive) {
			for (Node child : children) {
				child.calculateTransforms(true);
			}
		}
	}

	public void calculateBoneTransforms (boolean recursive) {
		for (final NodePart part : parts) {
			if (part.invBoneBindTransforms == null || part.bones == null || part.invBoneBindTransforms.size != part.bones.length)
				continue;
			final int n = part.invBoneBindTransforms.size;
			for (int i = 0; i < n; i++)
				part.bones[i].set(part.invBoneBindTransforms.keys[i].globalTransform).mul(part.invBoneBindTransforms.values[i]);
		}
		if (recursive) {
			for (Node child : children) {
				child.calculateBoneTransforms(true);
			}
		}
	}

	/** Calculate the bounding box of this Node. This is a potential slow operation, it is advised to cache the result. */
	public BoundingBox calculateBoundingBox (final BoundingBox out) {
		out.inf();
		return extendBoundingBox(out);
	}

	/** Calculate the bounding box of this Node. This is a potential slow operation, it is advised to cache the result. */
	public BoundingBox calculateBoundingBox (final BoundingBox out, boolean transform) {
		out.inf();
		return extendBoundingBox(out, transform);
	}

	/** Extends the bounding box with the bounds of this Node. This is a potential slow operation, it is advised to cache the
	 * result. */
	public BoundingBox extendBoundingBox (final BoundingBox out) {
		return extendBoundingBox(out, true);
	}

	/** Extends the bounding box with the bounds of this Node. This is a potential slow operation, it is advised to cache the
	 * result. */
	public BoundingBox extendBoundingBox (final BoundingBox out, boolean transform) {
		final int partCount = parts.size;
		for (int i = 0; i < partCount; i++) {
			final NodePart part = parts.get(i);
			if (part.enabled) {
				final MeshPart meshPart = part.meshPart;
				if (transform)
					meshPart.mesh.extendBoundingBox(out, meshPart.offset, meshPart.size, globalTransform);
				else
					meshPart.mesh.extendBoundingBox(out, meshPart.offset, meshPart.size);
			}
		}
		final int childCount = children.size;
		for (int i = 0; i < childCount; i++)
			children.get(i).extendBoundingBox(out);
		return out;
	}

	/** Adds this node as child to specified parent Node, synonym for: parent.addChild(this)
	 * @param parent The Node to attach this Node to. */
	public  void attachTo (T parent) {
		parent.addChild(this);
	}

	/** Removes this node from its current parent, if any. Short for: this.getParent().removeChild(this) */
	public void detach () {
		if (parent != null) {
			parent.removeChild(this);
			parent = null;
		}
	}

	/** @return whether this Node has one or more children (true) or not (false) */
	public boolean hasChildren () {
		return children != null && children.size > 0;
	}

	/** @return The number of child nodes that this Node current contains.
	 * @see #getChild(int) */
	public int getChildCount () {
		return children.size;
	}

	/** @param index The zero-based index of the child node to get, must be: 0 <= index < {@link #getChildCount()}.
	 * @return The child node at the specified index */
	public Node getChild (final int index) {
		return children.get(index);
	}

	/** @param recursive false to fetch a root child only, true to search the entire node tree for the specified node.
	 * @return The node with the specified id, or null if not found. */
	public Node getChild (final String id, boolean recursive, boolean ignoreCase) {
		return getNode(children, id, recursive, ignoreCase);
	}

	/** 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
	 * removed from its current parent.
	 * @param child The Node to add as child of this Node
	 * @return the zero-based index of the child */
	public  int addChild (final T child) {
		return insertChild(-1, child);
	}

	/** Adds the specified nodes as the currently last child of this node. If the node is already a child of another node, then it
	 * is removed from its current parent.
	 * @param nodes The Node to add as child of this Node
	 * @return the zero-based index of the first added child */
	public  int addChildren (final Iterable nodes) {
		return insertChildren(-1, nodes);
	}

	/** Insert the specified node as child of this node at the specified index. If the node is already a child of another node, then
	 * it is removed from its current parent. If the specified index is less than zero or equal or greater than
	 * {@link #getChildCount()} then the Node is added as the currently last child.
	 * @param index The zero-based index at which to add the child
	 * @param child The Node to add as child of this Node
	 * @return the zero-based index of the child */
	public  int insertChild (int index, final T child) {
		for (Node p = this; p != null; p = p.getParent()) {
			if (p == child) throw new GdxRuntimeException("Cannot add a parent as a child");
		}
		Node p = child.getParent();
		if (p != null && !p.removeChild(child)) throw new GdxRuntimeException("Could not remove child from its current parent");
		if (index < 0 || index >= children.size) {
			index = children.size;
			children.add(child);
		} else
			children.insert(index, child);
		child.parent = this;
		return index;
	}

	/** Insert the specified nodes as children of this node at the specified index. If the node is already a child of another node,
	 * then it is removed from its current parent. If the specified index is less than zero or equal or greater than
	 * {@link #getChildCount()} then the Node is added as the currently last child.
	 * @param index The zero-based index at which to add the child
	 * @param nodes The nodes to add as child of this Node
	 * @return the zero-based index of the first inserted child */
	public  int insertChildren (int index, final Iterable nodes) {
		if (index < 0 || index > children.size) index = children.size;
		int i = index;
		for (T child : nodes)
			insertChild(i++, child);
		return index;
	}

	/** Removes the specified node as child of this node. On success, the child node will be not attached to any parent node (its
	 * {@link #getParent()} method will return null). If the specified node currently isn't a child of this node then the removal
	 * is considered to be unsuccessful and the method will return false.
	 * @param child The child node to remove.
	 * @return Whether the removal was successful. */
	public  boolean removeChild (final T child) {
		if (!children.removeValue(child, true)) return false;
		child.parent = null;
		return true;
	}

	/** @return An {@link Iterable} to all child nodes that this node contains. */
	public Iterable getChildren () {
		return children;
	}

	/** @return The parent node that holds this node as child node, may be null. */
	public Node getParent () {
		return parent;
	}

	/** @return Whether (true) is this Node is a child node of another node or not (false). */
	public boolean hasParent () {
		return parent != null;
	}

	/** Creates a nested copy of this Node, any child nodes are copied using this method as well. The {@link #parts} are copied
	 * using the {@link NodePart#copy()} method. Note that that method copies the material and nodes (bones) by reference. If you
	 * intend to use the copy in a different node tree (e.g. a different Model or ModelInstance) then you will need to update these
	 * references afterwards.
	 * 
	 * Override this method in your custom Node class to instantiate that class, in that case you should override the
	 * {@link #set(Node)} method as well. */
	public Node copy () {
		return new Node().set(this);
	}

	/** Creates a nested copy of this Node, any child nodes are copied using the {@link #copy()} method. This will detach this node
	 * from its parent, but does not attach it to the parent of node being copied. The {@link #parts} are copied using the
	 * {@link NodePart#copy()} method. Note that that method copies the material and nodes (bones) by reference. If you intend to
	 * use this node in a different node tree (e.g. a different Model or ModelInstance) then you will need to update these
	 * references afterwards.
	 * 
	 * Override this method in your custom Node class to copy any additional fields you've added.
	 * @return This Node for chaining */
	protected Node set (Node other) {
		detach();
		id = other.id;
		isAnimated = other.isAnimated;
		inheritTransform = other.inheritTransform;
		translation.set(other.translation);
		rotation.set(other.rotation);
		scale.set(other.scale);
		localTransform.set(other.localTransform);
		globalTransform.set(other.globalTransform);
		parts.clear();
		for (NodePart nodePart : other.parts) {
			parts.add(nodePart.copy());
		}
		children.clear();
		for (Node child : other.getChildren()) {
			addChild(child.copy());
		}
		return this;
	}

	/** Helper method to recursive fetch a node from an array
	 * @param recursive false to fetch a root node only, true to search the entire node tree for the specified node.
	 * @return The node with the specified id, or null if not found. */
	public static Node getNode (final Array nodes, final String id, boolean recursive, boolean ignoreCase) {
		final int n = nodes.size;
		Node node;
		if (ignoreCase) {
			for (int i = 0; i < n; i++)
				if ((node = nodes.get(i)).id.equalsIgnoreCase(id)) return node;
		} else {
			for (int i = 0; i < n; i++)
				if ((node = nodes.get(i)).id.equals(id)) return node;
		}
		if (recursive) {
			for (int i = 0; i < n; i++)
				if ((node = getNode(nodes.get(i).children, id, true, ignoreCase)) != null) return node;
		}
		return null;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy