/*******************************************************************************
* 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.keyframe;
import com.badlogic.gdx.graphics.g3d.materials.Material;
import com.badlogic.gdx.graphics.g3d.model.AnimatedModel;
import com.badlogic.gdx.graphics.g3d.model.Model;
import com.badlogic.gdx.graphics.g3d.model.SubMesh;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;
public class KeyframedModel implements AnimatedModel, Disposable {
public final KeyframedSubMesh[] subMeshes;
protected final KeyframedAnimation[] animations;
public KeyframedModel(KeyframedSubMesh[] subMeshes) {
this.subMeshes = subMeshes;
Array<KeyframedAnimation> meshAnims = subMeshes[0].animations.values().toArray();
animations = new KeyframedAnimation[meshAnims.size];
for (int i = 0; i < animations.length; i++) {
animations[i] = meshAnims.get(i);
}
checkValidity();
}
private void checkValidity() {
for (int i = 0; i < subMeshes.length; i++) {
if (subMeshes[i].animations.size != animations.length)
throw new GdxRuntimeException("number of animations in subMesh[0] is not the same in subMesh[" + i
+ "]. All sub-meshes must have the same animations and number of frames");
}
for (int i = 0; i < animations.length; i++) {
KeyframedAnimation anim = animations[i];
for (int j = 0; j < subMeshes.length; j++) {
KeyframedAnimation otherAnim = subMeshes[j].animations.get(anim.name);
if (otherAnim == null)
throw new GdxRuntimeException("animation '" + anim.name + "' missing in subMesh[" + j + "]");
if (otherAnim.frameDuration != anim.frameDuration)
throw new GdxRuntimeException("animation '" + anim.name + "' in subMesh[" + j
+ "] has different frame duration than the same animation in subMesh[0]");
if (otherAnim.keyframes.length != anim.keyframes.length)
throw new GdxRuntimeException("animation '" + anim.name + "' in subMesh[" + j
+ "] has different number of keyframes than the same animation in subMesh[0]");
}
}
}
@Override
public void render() {
int len = subMeshes.length;
for (int i = 0; i < len; i++) {
KeyframedSubMesh subMesh = subMeshes[i];
if (i == 0 || !subMeshes[i - 1].material.equals(subMesh.material)) {
subMesh.material.bind();
}
subMesh.mesh.render(subMesh.primitiveType);
}
}
@Override
public void render(ShaderProgram program) {
int len = subMeshes.length;
for (int i = 0; i < len; i++) {
KeyframedSubMesh subMesh = subMeshes[i];
if (i == 0 || !subMeshes[i - 1].material.equals(subMesh.material)) {
subMesh.material.bind(program);
}
subMesh.mesh.render(program, subMesh.primitiveType);
}
}
@Override
public void setMaterials(Material... materials) {
if (materials.length != subMeshes.length)
throw new UnsupportedOperationException("number of materials must equal number of sub-meshes");
int len = materials.length;
for (int i = 0; i < len; i++) {
subMeshes[i].material = materials[i];
}
}
@Override
public void setMaterial(Material material) {
int len = subMeshes.length;
for (int i = 0; i < len; i++) {
subMeshes[i].material = material;
}
}
@Override
public KeyframedSubMesh getSubMesh(String name) {
int len = subMeshes.length;
for (int i = 0; i < len; i++) {
if (subMeshes[i].name.equals(name))
return subMeshes[i];
}
return null;
}
@Override
public SubMesh[] getSubMeshes() {
return subMeshes;
}
@Override
public void setAnimation(String animation, float time, boolean loop) {
int len = subMeshes.length;
for (int i = 0; i < len; i++) {
final KeyframedSubMesh subMesh = subMeshes[i];
final KeyframedAnimation anim = subMesh.animations.get(animation);
if (anim == null)
throw new IllegalArgumentException("No animation with name '" + animation + "' in submesh #" + i);
if (time < 0 || time > anim.totalDuration)
throw new IllegalArgumentException("time must be 0 <= time <= animation duration");
final int startIndex = (int) Math.floor((time / anim.frameDuration));
final Keyframe startFrame = anim.keyframes[startIndex];
final Keyframe endFrame = anim.keyframes[anim.keyframes.length - 1 == startIndex ? loop ? 0 : startIndex
: startIndex + 1];
final int numComponents = subMesh.animatedComponents;
final float[] src = startFrame.vertices;
final int srcLen = numComponents * subMesh.mesh.getNumVertices();
final float[] dst = subMesh.blendedVertices;
final int dstInc = subMesh.mesh.getVertexSize() / 4 - numComponents;
if (startFrame == endFrame) {
for (int srcIdx = 0, dstIdx = 0; srcIdx < srcLen; dstIdx += dstInc) {
for (int j = 0; j < numComponents; j++) {
dst[dstIdx++] = src[srcIdx++];
}
}
} else {
float[] src2 = endFrame.vertices;
float alpha = (time - (startIndex * anim.frameDuration)) / anim.frameDuration;
for (int srcIdx = 0, dstIdx = 0; srcIdx < srcLen; dstIdx += dstInc) {
for (int j = 0; j < numComponents; j++) {
final float valSrc = src[srcIdx];
final float valSrc2 = src2[srcIdx++];
dst[dstIdx++] = valSrc + (valSrc2 - valSrc) * alpha;
}
}
}
subMesh.mesh.setVertices(dst);
}
}
@Override
public KeyframedAnimation getAnimation(String name) {
return subMeshes[0].animations.get(name);
}
@Override
public KeyframedAnimation[] getAnimations() {
return animations;
}
@Override
public Model getSubModel(String... subMeshNames) {
// FIXME
return null;
}
private final static BoundingBox tmpBox = new BoundingBox();
@Override
public void getBoundingBox(BoundingBox bbox) {
bbox.inf();
for (int i = 0; i < subMeshes.length; i++) {
subMeshes[i].mesh.calculateBoundingBox(tmpBox);
bbox.ext(tmpBox);
}
}
@Override
public void dispose() {
for (int i = 0; i < subMeshes.length; i++) {
subMeshes[i].mesh.dispose();
}
}
}