com.badlogic.gdx.graphics.g3d.ModelInstance Maven / Gradle / Ivy
The 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;
import com.badlogic.gdx.graphics.g3d.model.Animation;
import com.badlogic.gdx.graphics.g3d.model.Node;
import com.badlogic.gdx.graphics.g3d.model.NodeAnimation;
import com.badlogic.gdx.graphics.g3d.model.NodeKeyframe;
import com.badlogic.gdx.graphics.g3d.model.NodePart;
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.ArrayMap;
import com.badlogic.gdx.utils.Pool;
/** An instance of a {@link Model}, allows to specify global transform and modify the materials, as it has a copy of the model's
* materials. Multiple instances can be created from the same Model, all sharing the meshes and textures of the Model. The Model
* owns the meshes and textures, to dispose of these, the Model has to be disposed. Therefor, the Model must outlive all its
* ModelInstances
*
*
* The ModelInstance creates a full copy of all materials, nodes and animations.
* @author badlogic, xoppa */
public class ModelInstance implements RenderableProvider {
/** Whether, by default, {@link NodeKeyframe}'s are shared amongst {@link Model} and ModelInstance. Can be overridden per
* ModelInstance using the constructor argument. */
public static boolean defaultShareKeyframes = true;
/** 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 **/
public final Array materials = new Array();
/** root nodes of the model **/
public final Array nodes = new Array();
/** animations of the model, modifying node transformations **/
public final Array animations = new Array();
/** the {@link Model} this instances derives from **/
public final Model model;
/** the world transform **/
public Matrix4 transform;
/** user definable value, which is passed to the {@link Shader}. */
public Object userData;
/** Constructs a new ModelInstance with all nodes and materials of the given model.
* @param model The {@link Model} to create an instance of. */
public ModelInstance (final Model model) {
this(model, (String[])null);
}
/** @param model The source {@link Model}
* @param nodeId The ID of the root {@link Node} of the {@link Model} for the instance to contain
* @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
public ModelInstance (final Model model, final String nodeId, boolean mergeTransform) {
this(model, null, nodeId, false, false, mergeTransform);
}
/** @param model The source {@link Model}
* @param transform The {@link Matrix4} instance for this ModelInstance to reference or null to create a new matrix.
* @param nodeId The ID of the root {@link Node} of the {@link Model} for the instance to contain
* @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
public ModelInstance (final Model model, final Matrix4 transform, final String nodeId, boolean mergeTransform) {
this(model, transform, nodeId, false, false, mergeTransform);
}
/** Recursively searches the mode for the specified node.
* @param model The source {@link Model}
* @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
* @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
* @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
public ModelInstance (final Model model, final String nodeId, boolean parentTransform, boolean mergeTransform) {
this(model, null, nodeId, true, parentTransform, mergeTransform);
}
/** Recursively searches the mode for the specified node.
* @param model The source {@link Model}
* @param transform The {@link Matrix4} instance for this ModelInstance to reference or null to create a new matrix.
* @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
* @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
* @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
public ModelInstance (final Model model, final Matrix4 transform, final String nodeId, boolean parentTransform,
boolean mergeTransform) {
this(model, transform, nodeId, true, parentTransform, mergeTransform);
}
/** @param model The source {@link Model}
* @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
* @param recursive True to recursively search the Model's node tree, false to only search for a root node
* @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
* @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
public ModelInstance (final Model model, final String nodeId, boolean recursive, boolean parentTransform,
boolean mergeTransform) {
this(model, null, nodeId, recursive, parentTransform, mergeTransform);
}
/** @param model The source {@link Model}
* @param transform The {@link Matrix4} instance for this ModelInstance to reference or null to create a new matrix.
* @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
* @param recursive True to recursively search the Model's node tree, false to only search for a root node
* @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
* @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
public ModelInstance (final Model model, final Matrix4 transform, final String nodeId, boolean recursive,
boolean parentTransform, boolean mergeTransform) {
this(model, transform, nodeId, recursive, parentTransform, mergeTransform, defaultShareKeyframes);
}
/** @param model The source {@link Model}
* @param transform The {@link Matrix4} instance for this ModelInstance to reference or null to create a new matrix.
* @param nodeId The ID of the {@link Node} within the {@link Model} for the instance to contain
* @param recursive True to recursively search the Model's node tree, false to only search for a root node
* @param parentTransform True to apply the parent's node transform to the instance (only applicable if recursive is true).
* @param mergeTransform True to apply the source node transform to the instance transform, resetting the node transform. */
public ModelInstance (final Model model, final Matrix4 transform, final String nodeId, boolean recursive,
boolean parentTransform, boolean mergeTransform, boolean shareKeyframes) {
this.model = model;
this.transform = transform == null ? new Matrix4() : transform;
Node copy, node = model.getNode(nodeId, recursive);
this.nodes.add(copy = node.copy());
if (mergeTransform) {
this.transform.mul(parentTransform ? node.globalTransform : node.localTransform);
copy.translation.set(0, 0, 0);
copy.rotation.idt();
copy.scale.set(1, 1, 1);
} else if (parentTransform && copy.hasParent()) this.transform.mul(node.getParent().globalTransform);
invalidate();
copyAnimations(model.animations, shareKeyframes);
calculateTransforms();
}
/** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
public ModelInstance (final Model model, final String... rootNodeIds) {
this(model, null, rootNodeIds);
}
/** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
public ModelInstance (final Model model, final Matrix4 transform, final String... rootNodeIds) {
this.model = model;
this.transform = transform == null ? new Matrix4() : transform;
if (rootNodeIds == null)
copyNodes(model.nodes);
else
copyNodes(model.nodes, rootNodeIds);
copyAnimations(model.animations, defaultShareKeyframes);
calculateTransforms();
}
/** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
public ModelInstance (final Model model, final Array rootNodeIds) {
this(model, null, rootNodeIds);
}
/** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
public ModelInstance (final Model model, final Matrix4 transform, final Array rootNodeIds) {
this(model, transform, rootNodeIds, defaultShareKeyframes);
}
/** Constructs a new ModelInstance with only the specified nodes and materials of the given model. */
public ModelInstance (final Model model, final Matrix4 transform, final Array rootNodeIds, boolean shareKeyframes) {
this.model = model;
this.transform = transform == null ? new Matrix4() : transform;
copyNodes(model.nodes, rootNodeIds);
copyAnimations(model.animations, shareKeyframes);
calculateTransforms();
}
/** Constructs a new ModelInstance at the specified position. */
public ModelInstance (final Model model, Vector3 position) {
this(model);
this.transform.setToTranslation(position);
}
/** Constructs a new ModelInstance at the specified position. */
public ModelInstance (final Model model, float x, float y, float z) {
this(model);
this.transform.setToTranslation(x, y, z);
}
/** Constructs a new ModelInstance with the specified transform. */
public ModelInstance (final Model model, Matrix4 transform) {
this(model, transform, (String[])null);
}
/** Constructs a new ModelInstance which is an copy of the specified ModelInstance. */
public ModelInstance (ModelInstance copyFrom) {
this(copyFrom, copyFrom.transform.cpy());
}
/** Constructs a new ModelInstance which is an copy of the specified ModelInstance. */
public ModelInstance (ModelInstance copyFrom, final Matrix4 transform) {
this(copyFrom, transform, defaultShareKeyframes);
}
/** Constructs a new ModelInstance which is an copy of the specified ModelInstance. */
public ModelInstance (ModelInstance copyFrom, final Matrix4 transform, boolean shareKeyframes) {
this.model = copyFrom.model;
this.transform = transform == null ? new Matrix4() : transform;
copyNodes(copyFrom.nodes);
copyAnimations(copyFrom.animations, shareKeyframes);
calculateTransforms();
}
/** @return A newly created ModelInstance which is a copy of this ModelInstance */
public ModelInstance copy () {
return new ModelInstance(this);
}
private void copyNodes (Array nodes) {
for (int i = 0, n = nodes.size; i < n; ++i) {
final Node node = nodes.get(i);
this.nodes.add(node.copy());
}
invalidate();
}
private void copyNodes (Array nodes, final String... nodeIds) {
for (int i = 0, n = nodes.size; i < n; ++i) {
final Node node = nodes.get(i);
for (final String nodeId : nodeIds) {
if (nodeId.equals(node.id)) {
this.nodes.add(node.copy());
break;
}
}
}
invalidate();
}
private void copyNodes (Array nodes, final Array nodeIds) {
for (int i = 0, n = nodes.size; i < n; ++i) {
final Node node = nodes.get(i);
for (final String nodeId : nodeIds) {
if (nodeId.equals(node.id)) {
this.nodes.add(node.copy());
break;
}
}
}
invalidate();
}
/** Makes sure that each {@link NodePart} of the {@link Node} and its sub-nodes, doesn't reference a node outside this node
* tree and that all materials are listed in the {@link #materials} array. */
private void invalidate (Node node) {
for (int i = 0, n = node.parts.size; i < n; ++i) {
NodePart part = node.parts.get(i);
ArrayMap bindPose = part.invBoneBindTransforms;
if (bindPose != null) {
for (int j = 0; j < bindPose.size; ++j) {
bindPose.keys[j] = getNode(bindPose.keys[j].id);
}
}
if (!materials.contains(part.material, true)) {
final int midx = materials.indexOf(part.material, false);
if (midx < 0)
materials.add(part.material = part.material.copy());
else
part.material = materials.get(midx);
}
}
for (int i = 0, n = node.getChildCount(); i < n; ++i) {
invalidate(node.getChild(i));
}
}
/** Makes sure that each {@link NodePart} of each {@link Node} doesn't reference a node outside this node tree and that all
* materials are listed in the {@link #materials} array. */
private void invalidate () {
for (int i = 0, n = nodes.size; i < n; ++i) {
invalidate(nodes.get(i));
}
}
/** Copy source animations to this ModelInstance
* @param source Iterable collection of source animations {@link Animation} */
public void copyAnimations (final Iterable source) {
for (final Animation anim : source) {
copyAnimation(anim, defaultShareKeyframes);
}
}
/** Copy source animations to this ModelInstance
* @param source Iterable collection of source animations {@link Animation}
* @param shareKeyframes Shallow copy of {@link NodeKeyframe}'s if it's true, otherwise make a deep copy. */
public void copyAnimations (final Iterable source, boolean shareKeyframes) {
for (final Animation anim : source) {
copyAnimation(anim, shareKeyframes);
}
}
/** Copy the source animation to this ModelInstance
* @param sourceAnim The source animation {@link Animation} */
public void copyAnimation (Animation sourceAnim) {
copyAnimation(sourceAnim, defaultShareKeyframes);
}
/** Copy the source animation to this ModelInstance
* @param sourceAnim The source animation {@link Animation}
* @param shareKeyframes Shallow copy of {@link NodeKeyframe}'s if it's true, otherwise make a deep copy. */
public void copyAnimation (Animation sourceAnim, boolean shareKeyframes) {
Animation animation = new Animation();
animation.id = sourceAnim.id;
animation.duration = sourceAnim.duration;
for (final NodeAnimation nanim : sourceAnim.nodeAnimations) {
final Node node = getNode(nanim.node.id);
if (node == null) continue;
NodeAnimation nodeAnim = new NodeAnimation();
nodeAnim.node = node;
if (shareKeyframes) {
nodeAnim.translation = nanim.translation;
nodeAnim.rotation = nanim.rotation;
nodeAnim.scaling = nanim.scaling;
} else {
if (nanim.translation != null) {
nodeAnim.translation = new Array>();
for (final NodeKeyframe kf : nanim.translation)
nodeAnim.translation.add(new NodeKeyframe(kf.keytime, kf.value));
}
if (nanim.rotation != null) {
nodeAnim.rotation = new Array>();
for (final NodeKeyframe kf : nanim.rotation)
nodeAnim.rotation.add(new NodeKeyframe(kf.keytime, kf.value));
}
if (nanim.scaling != null) {
nodeAnim.scaling = new Array>();
for (final NodeKeyframe kf : nanim.scaling)
nodeAnim.scaling.add(new NodeKeyframe(kf.keytime, kf.value));
}
}
if (nodeAnim.translation != null || nodeAnim.rotation != null || nodeAnim.scaling != null)
animation.nodeAnimations.add(nodeAnim);
}
if (animation.nodeAnimations.size > 0) animations.add(animation);
}
/** Traverses the Node hierarchy and collects {@link Renderable} instances for every node with a graphical representation.
* Renderables are obtained from the provided pool. The resulting array can be rendered via a {@link ModelBatch}.
*
* @param renderables the output array
* @param pool the pool to obtain Renderables from */
public void getRenderables (Array renderables, Pool pool) {
for (Node node : nodes) {
getRenderables(node, renderables, pool);
}
}
/** @return The renderable of the first node's first part. */
public Renderable getRenderable (final Renderable out) {
return getRenderable(out, nodes.get(0));
}
/** @return The renderable of the node's first part. */
public Renderable getRenderable (final Renderable out, final Node node) {
return getRenderable(out, node, node.parts.get(0));
}
public Renderable getRenderable (final Renderable out, final Node node, final NodePart nodePart) {
nodePart.setRenderable(out);
if (nodePart.bones == null && transform != null)
out.worldTransform.set(transform).mul(node.globalTransform);
else if (transform != null)
out.worldTransform.set(transform);
else
out.worldTransform.idt();
out.userData = userData;
return out;
}
protected void getRenderables (Node node, Array renderables, Pool pool) {
if (node.parts.size > 0) {
for (NodePart nodePart : node.parts) {
if (nodePart.enabled) renderables.add(getRenderable(pool.obtain(), node, nodePart));
}
}
for (Node child : node.getChildren()) {
getRenderables(child, renderables, pool);
}
}
/** Calculates the local and world transform of all {@link Node} instances in this model, recursively. First each
* {@link Node#localTransform} transform is calculated based on the translation, rotation and scale of each Node. Then each
* {@link Node#calculateWorldTransform()} is calculated, based on the parent's world transform and the local transform of each
* Node. Finally, the animation bone matrices are updated accordingly.
*
*
* This method can be used to recalculate all transforms if any of the Node's local properties (translation, rotation, scale)
* was modified. */
public void calculateTransforms () {
final int n = nodes.size;
for (int i = 0; i < n; i++) {
nodes.get(i).calculateTransforms(true);
}
for (int i = 0; i < n; i++) {
nodes.get(i).calculateBoneTransforms(true);
}
}
/** Calculate the bounding box of this model instance. This is a potential slow operation, it is advised to cache the result.
* @param out the {@link BoundingBox} that will be set with the bounds.
* @return the out parameter for chaining */
public BoundingBox calculateBoundingBox (final BoundingBox out) {
out.inf();
return extendBoundingBox(out);
}
/** Extends the bounding box with the bounds of this model instance. This is a potential slow operation, it is advised to cache
* the result.
* @param out the {@link BoundingBox} that will be extended with the bounds.
* @return the out parameter for chaining */
public BoundingBox extendBoundingBox (final BoundingBox out) {
final int n = nodes.size;
for (int i = 0; i < n; i++)
nodes.get(i).extendBoundingBox(out);
return out;
}
/** @param id The ID of the animation to fetch (case sensitive).
* @return The {@link Animation} with the specified id, or null if not available. */
public Animation getAnimation (final String id) {
return getAnimation(id, false);
}
/** @param id The ID of the animation to fetch.
* @param ignoreCase whether to use case sensitivity when comparing the animation id.
* @return The {@link Animation} with the specified id, or null if not available. */
public Animation getAnimation (final String id, boolean ignoreCase) {
final int n = animations.size;
Animation animation;
if (ignoreCase) {
for (int i = 0; i < n; i++)
if ((animation = animations.get(i)).id.equalsIgnoreCase(id)) return animation;
} else {
for (int i = 0; i < n; i++)
if ((animation = animations.get(i)).id.equals(id)) return animation;
}
return null;
}
/** @param id The ID of the material to fetch.
* @return The {@link Material} with the specified id, or null if not available. */
public Material getMaterial (final String id) {
return getMaterial(id, true);
}
/** @param id The ID of the material to fetch.
* @param ignoreCase whether to use case sensitivity when comparing the material id.
* @return The {@link Material} with the specified id, or null if not available. */
public Material getMaterial (final String id, boolean ignoreCase) {
final int n = materials.size;
Material material;
if (ignoreCase) {
for (int i = 0; i < n; i++)
if ((material = materials.get(i)).id.equalsIgnoreCase(id)) return material;
} else {
for (int i = 0; i < n; i++)
if ((material = materials.get(i)).id.equals(id)) return material;
}
return null;
}
/** @param id The ID of the node to fetch.
* @return The {@link Node} with the specified id, or null if not found. */
public Node getNode (final String id) {
return getNode(id, true);
}
/** @param id The ID of the node to fetch.
* @param recursive false to fetch a root node only, true to search the entire node tree for the specified node.
* @return The {@link Node} with the specified id, or null if not found. */
public Node getNode (final String id, boolean recursive) {
return getNode(id, recursive, false);
}
/** @param id The ID of the node to fetch.
* @param recursive false to fetch a root node only, true to search the entire node tree for the specified node.
* @param ignoreCase whether to use case sensitivity when comparing the node id.
* @return The {@link Node} with the specified id, or null if not found. */
public Node getNode (final String id, boolean recursive, boolean ignoreCase) {
return Node.getNode(nodes, id, recursive, ignoreCase);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy