/******************************************************************************* * 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(); } } }