com.badlogic.gdx.graphics.g3d.keyframed.KeyframedModel 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.keyframed;
import java.util.ArrayList;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g3d.Animator;
import com.badlogic.gdx.graphics.g3d.Animator.WrapMode;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Animation;
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Animator;
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Joints;
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Model;
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Renderer;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.ObjectMap;
/** An animated model with {@link KeyframeAnimation}s. Currently the animations can only be instanced from an {@link MD5Animation}.
* Support for binary serialization may be included in loadFuture development.
*
* @author Dave Clayton */
public class KeyframedModel {
private Material[] materials;
private static ObjectMap animations = null;
private ArrayList animationRefs = new ArrayList();
private String assetName;
private KeyframeAnimator animator = null;
private Mesh[] target = null;
private boolean[] visible = null;
private int numMeshes = 0;
private ArrayList taggedJointNames = new ArrayList();
/** Gets the {@link KeyframeAnimator} for this model.
* @return the animator. */
public Animator getAnimator () {
return animator;
}
/** Sets the {@link Material} list for this model, one for each mesh, in mesh order.
* @param mats An array of materials. */
public void setMaterials (Material[] mats) {
materials = new Material[mats.length];
for (int i = 0; i < mats.length; i++) {
materials[i] = mats[i];
}
}
/** Sets the tagged joints for this model's animations. Tagged joints have their data preserved post-sampling.
* @param joints An array of joint names. */
public void setTaggedJoints (ArrayList joints) {
taggedJointNames = joints;
}
// TODO: Split this out to an MD5toKeyframe loader in com.badlogic.gdx.graphics.loaders
/** Loads a single {@link KeyframeAnimation} from an {@link MD5Animation}, then stores it in the animation dictionary for
* runtime playback. The dictionary manages ref counted animations so you do not have multiple copies of 100k animations in
* memory when you only need one per unique MD5 model. You must call dispose() when finished with this class.
* @param md5model The source {@link MD5Model}.
* @param md5renderer An {@link MD5Renderer} instance, used to calculate the skinned geometry.
* @param md5animator An {@link MD5Animator} instance to control the MD5 animation cycle the keyframing samples from.
* @param md5animation The {@link MD5Animation} to sample.
* @param sampleRate The sample rate (use smaller values for smoother animation but greater memory usage). Recommended value:
* 0.3
* @param modelAsset The name of the model asset. Must be unique to the model. Using its path is recommended
* @param animKey The name used to store the animation in the mode's animation map. */
public KeyframeAnimation sampleAnimationFromMD5 (MD5Model md5model, MD5Renderer md5renderer, MD5Animator md5animator,
MD5Animation md5animation, float sampleRate, String modelAsset, String animKey) {
this.assetName = modelAsset;
numMeshes = md5model.meshes.length;
boolean cached = false;
if (animator == null) {
animator = new KeyframeAnimator(numMeshes, sampleRate);
target = new Mesh[numMeshes];
visible = new boolean[numMeshes];
for (int i = 0; i < visible.length; i++) {
visible[i] = true;
}
}
if (animations == null) {
animations = new ObjectMap();
}
String key = modelAsset + "_" + animKey;
KeyframeAnimation a = null;
if (animations.containsKey(key)) {
a = animations.get(key);
a.addRef();
cached = true;
}
animationRefs.add(key);
md5animator.setAnimation(md5animation, WrapMode.Clamp);
float len = md5animation.frames.length * md5animation.secondsPerFrame;
int numSamples = (int)(len / sampleRate) + 1;
if (!cached) {
a = new KeyframeAnimation(md5animation.name, numSamples, len, sampleRate);
animations.put(key, a);
}
md5animator.update(0.1f);
md5renderer.setSkeleton(md5animator.getSkeleton());
int i = 0;
int numVertices = 0, numIndices = 0;
for (float t = 0; t < len; t += sampleRate) {
// store meshes.
Keyframe k = null;
if (!cached) {
k = new Keyframe();
k.vertices = new float[numMeshes][];
k.indices = new short[numMeshes][];
if (taggedJointNames.size() > 0) {
k.taggedJointPos = new Vector3[taggedJointNames.size()];
k.taggedJoint = new Quaternion[taggedJointNames.size()];
}
}
for (int m = 0; m < numMeshes; m++) {
float vertices[] = md5renderer.getVertices(m);
short indices[] = md5renderer.getIndices(m);
numVertices = vertices.length;
numIndices = indices.length;
if (!cached) {
k.vertices[m] = clone(vertices);
k.indices[m] = clone(indices);
}
if (target[m] == null) {
animator.setKeyframeDimensions(m, numVertices, numIndices);
animator.setNumTaggedJoints(taggedJointNames.size());
VertexAttributes attribs = md5renderer.getMesh().getVertexAttributes();
target[m] = new Mesh(false, numVertices, numIndices, attribs);
if (target[m].getVertexSize() / 4 != KeyframeAnimator.sStride)
throw new GdxRuntimeException("Mesh vertexattributes != 8 - is this a valid MD5 source mesh?");
}
}
if (!cached) {
// store tagged joints.
MD5Joints skel = md5animator.getSkeleton();
for (int tj = 0; tj < taggedJointNames.size(); tj++) {
String name = taggedJointNames.get(tj);
for (int j = 0; j < skel.numJoints; j++) {
if (name.equals(skel.names[j])) {
int idx = j * 8;
// FIXME what is this? float p = skel.joints[idx];
float x = skel.joints[idx + 1];
float y = skel.joints[idx + 2];
float z = skel.joints[idx + 3];
k.taggedJointPos[tj] = new Vector3(x, y, z);
float qx = skel.joints[idx + 4];
float qy = skel.joints[idx + 5];
float qz = skel.joints[idx + 6];
float qw = skel.joints[idx + 7];
k.taggedJoint[tj] = new Quaternion(qx, qy, qz, qw);
break;
}
}
}
a.keyframes[i] = k;
}
md5animator.update(sampleRate);
md5renderer.setSkeleton(md5animator.getSkeleton());
i++;
}
if (cached) {
// Gdx.app.log("Loader",
// "Added ref to animation "+key+" - keyframes ("+i+" keyframes generated). animations.size = "+animations.size);
} else {
// Gdx.app.log("Loader",
// "Loaded animation "+key+" - keyframes ("+i+" keyframes generated). animations.size = "+animations.size);
}
return a;
}
private short[] clone (short[] indices) {
short[] tmp = new short[indices.length];
System.arraycopy(indices, 0, tmp, 0, indices.length);
return tmp;
}
private float[] clone (float[] vertices) {
float[] tmp = new float[vertices.length];
System.arraycopy(vertices, 0, tmp, 0, vertices.length);
return tmp;
}
public void getJointData (int tagIndex, Vector3 pos, Quaternion orient) {
Keyframe kf = animator.getInterpolatedKeyframe();
pos.set(kf.taggedJointPos[tagIndex]);
orient.x = kf.taggedJoint[tagIndex].x;
orient.y = kf.taggedJoint[tagIndex].y;
orient.z = kf.taggedJoint[tagIndex].z;
orient.w = kf.taggedJoint[tagIndex].w;
}
/** Set the current playing animation.
* @param animKey The name of the animation.
* @param wrapMode The animation {@link WrapMode}. */
public void setAnimation (String animKey, WrapMode wrapMode) {
KeyframeAnimation anim = getAnimation(animKey);
if (anim != null) {
animator.setAnimation(anim, wrapMode);
animator.getInterpolatedKeyframe().indicesSet = false;
animator.getInterpolatedKeyframe().indicesSent = false;
}
}
/** Gets the specified {@link KeyframeAnimation} from the animation map.
* @param animKey The name of the animation.
* @return The animation. */
public KeyframeAnimation getAnimation (String animKey) {
return animations.get(assetName + "_" + animKey);
}
/** Updates the model, causing the model's {@link KeyframeAnimator} to interpolate the animation and update the render geometry.
* @param dt Delta time since last frame. */
public void update (float dt) {
if (animator != null) {
animator.update(dt);
if (animator.hasAnimation()) {
Keyframe ikf = animator.getInterpolatedKeyframe();
if (animator.getCurrentWrapMode() == WrapMode.SingleFrame && ikf.indicesSent) return; /*
* early out for single frame
* animations
*/
// send our target index and vertex data to the target mesh
for (int i = 0; i < numMeshes; i++) {
target[i].setVertices(ikf.vertices[i]);
if (!ikf.indicesSent) {
target[i].setIndices(ikf.indices[i]);
}
}
ikf.indicesSent = true;
}
}
}
/** Draws the model using the current interpolated animation frame and the material list set by {@link #setMaterials}.
* {@link #update} must be called prior to this. */
public void render () {
for (int i = 0; i < numMeshes; i++) {
// bind textures etc.
Material mat = materials[i];
if (mat != null) {
if (mat.Texture != null) {
mat.Texture.bind();
}
mat.set(GL10.GL_FRONT);
}
if (visible[i]) target[i].render(GL10.GL_TRIANGLES, 0, target[i].getNumIndices());
}
}
/** Sets the specified mesh's visibility (MD5 models typically consist of a number of meshes).
* @param idx the mesh's index (same order as found in the .md5mesh file)
* @param visible whether the mesh should be drawn or not */
public void setMeshVisible (int idx, boolean visible) {
this.visible[idx] = visible;
}
public void dispose () {
for (String key : animationRefs) {
KeyframeAnimation anim = animations.get(key);
if (anim.removeRef() == 0) {
// Gdx.app.log("Engine", "Removing anim "+key+" from dict. Dict size = "+animations.size);
animations.remove(key);
}
}
for (Mesh m : target) {
if (m != null) {
m.dispose();
}
}
// Gdx.app.log("Engine", "Disposed kfmodel "+this.assetName+", "+animations.size+" anims remain in cache");
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy