[Changed] WrapMode for animators with additional 'SingleFrame' mode to only play 1 frame of animation then stop. This is to get models out of bind pose into a 'frozen idle' with very little performance cost.
[Changed] Added binary serialization of MD5Animation and MD5Model; intended for asset compiling usage.
[Fixed] Memory leak with TextureDict not disposing textures correctly when unloadAll() called.
/**\r
* Unloads all of the currently managed textures.\r
*/\r
- public static void unloadAll() //TODO: check this calls dispose on the textures\r
+ public static void unloadAll()\r
{\r
+ for(TextureRef tex : sDictionary.values())\r
+ {\r
+ tex.dispose();\r
+ }\r
sDictionary.clear();\r
}\r
}\r
public abstract class Animator {\r
protected float mAnimPos = 0.f;\r
protected float mAnimLen = 0.f;\r
- protected boolean mAnimLoop = false;\r
+ protected WrapMode mWrapMode = WrapMode.Clamp;\r
protected int mCurrentFrameIdx = -1;\r
protected int mNextFrameIdx = -1;\r
protected float mFrameDelta = 0.f;\r
protected Animation mCurrentAnim = null;\r
\r
+ public enum WrapMode\r
+ {\r
+ Loop,\r
+ Clamp,\r
+ //PingPong, //TODO\r
+ SingleFrame,\r
+ }\r
+ \r
/**\r
* Sets the currently playing {@link Animation}.\r
* @param anim\r
* The animation to play.\r
- * @param loop\r
- * Whether to loop the animation.\r
+ * @param mode\r
+ * The animation's {@link WrapMode}.\r
*/\r
- public void setAnimation(Animation anim, boolean loop)\r
+ public void setAnimation(Animation anim, WrapMode mode)\r
{\r
mCurrentAnim = anim;\r
- mAnimLoop = loop;\r
+ mWrapMode = mode;\r
\r
mAnimPos = mFrameDelta = 0.f;\r
mCurrentFrameIdx = -1;\r
if(mCurrentAnim != null)\r
{\r
mAnimLen = mCurrentAnim.getLength(); \r
- } \r
+ }\r
}\r
\r
/**\r
{\r
return mCurrentAnim;\r
}\r
+ \r
+ /**\r
+ * Gets the current animation {@link WrapMode}.\r
+ * @return the current wrapmode.\r
+ */\r
+ public WrapMode getCurrentWrapMode()\r
+ {\r
+ return mWrapMode;\r
+ }\r
\r
/**\r
* Updates the controller.\r
{\r
if(mCurrentAnim != null)\r
{\r
- mAnimPos += dt;\r
- if(mAnimPos > mAnimLen)\r
+ if(mWrapMode != WrapMode.SingleFrame)\r
{\r
- if(mAnimLoop)\r
+ mAnimPos += dt;\r
+ if(mAnimPos > mAnimLen)\r
{\r
- mAnimPos = 0.f;\r
- }\r
- else\r
- {\r
- mAnimPos = mAnimLen;\r
+ if(mWrapMode == WrapMode.Loop)\r
+ {\r
+ mAnimPos = 0.f;\r
+ }\r
+ else if(mWrapMode == WrapMode.Clamp)\r
+ {\r
+ mAnimPos = mAnimLen;\r
+ }\r
}\r
}\r
// select the frames\r
}\r
else\r
{\r
- if(mAnimLoop)\r
- {\r
- mNextFrameIdx = 0;\r
- }\r
- else\r
+ switch(mWrapMode)\r
{\r
- mNextFrameIdx = currentFrameIdx;\r
+ case Loop:\r
+ case SingleFrame:\r
+ mNextFrameIdx = 0; break;\r
+ case Clamp:\r
+ mNextFrameIdx = currentFrameIdx; break;\r
}\r
}\r
\r
public String name;\r
public Keyframe[] keyframes;\r
public float length;\r
+ public float sampleRate;\r
public int refs;\r
\r
- public KeyframeAnimation(String name, int frames, float length)\r
+ public KeyframeAnimation(String name, int frames, float length, float sampleRate)\r
{\r
this.name = name;\r
this.keyframes = new Keyframe[frames];\r
this.length = length;\r
+ this.sampleRate = sampleRate;\r
this.refs = 1;\r
}\r
\r
*/\r
package com.badlogic.gdx.graphics.g3d.keyframed;\r
\r
+import com.badlogic.gdx.Gdx;\r
import com.badlogic.gdx.graphics.g3d.Animator;\r
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Quaternion;\r
import com.badlogic.gdx.math.Quaternion;\r
public class KeyframeAnimator extends Animator {\r
\r
// constants\r
- public static int sStride = 8; // hmmm.\r
+ public final static int sStride = 8; // TODO: fix hard-coded wrongness like this.\r
\r
private Keyframe A = null;\r
private Keyframe B = null;\r
static MD5Quaternion jointAOrient = new MD5Quaternion();\r
static MD5Quaternion jointBOrient = new MD5Quaternion();\r
\r
- //TODO: Optimise further if possible - this is the CPU bottleneck for animation\r
@Override\r
protected void interpolate()\r
{\r
+ if(mWrapMode == WrapMode.SingleFrame &&\r
+ R.indicesSet)\r
+ return;\r
+\r
float t = mFrameDelta*invSampleRate;\r
for(int i=0; i<numMeshes; i++)\r
{\r
- for(int n=0; n<A.vertices[i].length; n+=sStride)\r
+ float[] Rvertices = R.vertices[i];\r
+ float[] Avertices = A.vertices[i];\r
+ float[] Bvertices = B.vertices[i];\r
+ for(int n=0; n<Avertices.length; n+=sStride)\r
{\r
// interpolated position\r
- float Ax = A.vertices[i][n];\r
- float Bx = B.vertices[i][n];\r
+ float Ax = Avertices[n];\r
+ float Bx = Bvertices[n];\r
float Rx = Ax + (Bx - Ax)*t;\r
- float Ay = A.vertices[i][n+1];\r
- float By = B.vertices[i][n+1];\r
+ float Ay = Avertices[n+1];\r
+ float By = Bvertices[n+1];\r
float Ry = Ay + (By - Ay)*t;\r
- float Az = A.vertices[i][n+2];\r
- float Bz = B.vertices[i][n+2];\r
+ float Az = Avertices[n+2];\r
+ float Bz = Bvertices[n+2];\r
float Rz = Az + (Bz - Az)*t;\r
\r
- R.vertices[i][n] = Rx;\r
- R.vertices[i][n+1] = Ry;\r
- R.vertices[i][n+2] = Rz;\r
+ Rvertices[n] = Rx;\r
+ Rvertices[n+1] = Ry;\r
+ Rvertices[n+2] = Rz;\r
\r
// texture coordinates\r
- R.vertices[i][n+3] = A.vertices[i][n+3];\r
- R.vertices[i][n+4] = A.vertices[i][n+4];\r
+ Rvertices[n+3] = Avertices[n+3];\r
+ Rvertices[n+4] = Avertices[n+4];\r
\r
// interpolated normals\r
- Ax = A.vertices[i][n+5];\r
- Bx = B.vertices[i][n+5];\r
+ Ax = Avertices[n+5];\r
+ Bx = Bvertices[n+5];\r
Rx = Ax + (Bx - Ax)*t;\r
- Ay = A.vertices[i][n+6];\r
- By = B.vertices[i][n+6];\r
+ Ay = Avertices[n+6];\r
+ By = Bvertices[n+6];\r
Ry = Ay + (By - Ay)*t;\r
- Az = A.vertices[i][n+7];\r
- Bz = B.vertices[i][n+7];\r
+ Az = Avertices[n+7];\r
+ Bz = Bvertices[n+7];\r
Rz = Az + (Bz - Az)*t;\r
- R.vertices[i][n+5] = Rx;\r
- R.vertices[i][n+6] = Ry;\r
- R.vertices[i][n+7] = Rz;\r
+ Rvertices[n+5] = Rx;\r
+ Rvertices[n+6] = Ry;\r
+ Rvertices[n+7] = Rz;\r
}\r
\r
if(!R.indicesSet)\r
}\r
R.indicesSet = true;\r
\r
+ if(A.taggedJoint != null)\r
+ {\r
+ interpolateJoints(t);\r
+ }\r
+ }\r
+ \r
+ private void interpolateJoints(float t)\r
+ {\r
//interpolate any tagged joints\r
for(int tj = 0; tj<A.taggedJoint.length; tj++)\r
{\r
*/\r
package com.badlogic.gdx.graphics.g3d.keyframed;\r
\r
-import java.io.DataInputStream;\r
-import java.io.DataOutputStream;\r
import java.util.ArrayList;\r
\r
import com.badlogic.gdx.Gdx;\r
import com.badlogic.gdx.graphics.VertexAttributes;\r
import com.badlogic.gdx.graphics.g3d.Animator;\r
import com.badlogic.gdx.graphics.g3d.Material;\r
+import com.badlogic.gdx.graphics.g3d.Animator.WrapMode;\r
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Animation;\r
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Animator;\r
import com.badlogic.gdx.graphics.g3d.loaders.md5.MD5Joints;\r
*/\r
public Animator getAnimator() { return animator; }\r
\r
- public void write(DataOutputStream out)\r
- {\r
- throw new GdxRuntimeException("Not implemented");\r
- }\r
- \r
- public void read(DataInputStream in)\r
- { \r
- throw new GdxRuntimeException("Not implemented");\r
- }\r
- \r
/**\r
* Sets the {@link Material} list for this model, one for each mesh, in mesh order.\r
* @param mats\r
* @param animKey\r
* The name used to store the animation in the mode's animation map.\r
*/\r
- public void sampleAnimationFromMD5(MD5Model md5model, MD5Renderer md5renderer, MD5Animator md5animator, MD5Animation md5animation, float sampleRate, String modelAsset, String animKey)\r
+ public KeyframeAnimation sampleAnimationFromMD5(MD5Model md5model, MD5Renderer md5renderer, MD5Animator md5animator, MD5Animation md5animation, float sampleRate, String modelAsset, String animKey)\r
{\r
this.assetName = modelAsset;\r
numMeshes = md5model.meshes.length;\r
}\r
animationRefs.add(key);\r
\r
- md5animator.setAnimation(md5animation, false);\r
+ md5animator.setAnimation(md5animation, WrapMode.Clamp);\r
\r
float len = md5animation.frames.length*md5animation.secondsPerFrame;\r
int numSamples = (int)(len/sampleRate)+1;\r
\r
if(!cached)\r
{\r
- a = new KeyframeAnimation(md5animation.name, numSamples, len);\r
+ a = new KeyframeAnimation(md5animation.name, numSamples, len, sampleRate);\r
animations.put(key, a);\r
}\r
\r
{\r
//Gdx.app.log("Loader", "Loaded animation "+key+" - keyframes ("+i+" keyframes generated). animations.size = "+animations.size);\r
}\r
+ \r
+ return a;\r
}\r
\r
public void getJointData(int tagIndex, Vector3 pos, Quaternion orient)\r
* Set the current playing animation.\r
* @param animKey\r
* The name of the animation.\r
- * @param loop\r
- * Whether the animation should loop.\r
+ * @param wrapMode\r
+ * The animation {@link WrapMode}.\r
*/\r
- public void setAnimation(String animKey, boolean loop)\r
+ public void setAnimation(String animKey, WrapMode wrapMode)\r
{\r
KeyframeAnimation anim = getAnimation(animKey);\r
- animator.setAnimation(anim, loop);\r
+ animator.setAnimation(anim, wrapMode);\r
+ animator.getInterpolatedKeyframe().indicesSet = false;\r
+ animator.getInterpolatedKeyframe().indicesSent = false;\r
}\r
\r
/**\r
{\r
Keyframe ikf = animator.getInterpolatedKeyframe();\r
\r
+ if(animator.getCurrentWrapMode() == WrapMode.SingleFrame &&\r
+ ikf.indicesSent)\r
+ return; /* early out for single frame animations */\r
+ \r
// send our target index and vertex data to the target mesh\r
for(int i=0; i<numMeshes; i++)\r
{\r
animations.remove(key);\r
}\r
}\r
+ for(Mesh m : target)\r
+ {\r
+ if(m != null)\r
+ {\r
+ m.dispose();\r
+ }\r
+ }\r
+ //Gdx.app.log("Engine", "Disposed kfmodel "+this.assetName+", "+animations.size+" anims remain in cache");\r
}\r
}\r
\r
package com.badlogic.gdx.graphics.g3d.loaders.md5;\r
\r
+import java.io.DataInputStream;\r
+import java.io.DataOutputStream;\r
+import java.io.IOException;\r
+\r
import com.badlogic.gdx.graphics.g3d.Animation;\r
import com.badlogic.gdx.math.collision.BoundingBox;\r
\r
public BoundingBox[] bounds;\r
public String name;\r
\r
+ public void write(DataOutputStream out) throws IOException\r
+ {\r
+ out.writeUTF(name);\r
+ out.writeInt(frameRate);\r
+ out.writeFloat(secondsPerFrame);\r
+ out.writeInt(frames.length);\r
+ for(int i=0; i<frames.length; i++)\r
+ {\r
+ frames[i].write(out);\r
+ }\r
+ out.writeInt(bounds.length);\r
+ for(int i=0; i<bounds.length; i++)\r
+ {\r
+ out.writeFloat(bounds[i].min.x);\r
+ out.writeFloat(bounds[i].min.y);\r
+ out.writeFloat(bounds[i].min.z);\r
+ out.writeFloat(bounds[i].max.x);\r
+ out.writeFloat(bounds[i].max.y);\r
+ out.writeFloat(bounds[i].max.z);\r
+ }\r
+ }\r
+ \r
+ public void read(DataInputStream in) throws IOException\r
+ {\r
+ name = in.readUTF();\r
+ frameRate = in.readInt();\r
+ secondsPerFrame = in.readFloat();\r
+ int numFrames = in.readInt();\r
+ frames = new MD5Joints[numFrames];\r
+ for(int i=0; i<numFrames; i++)\r
+ {\r
+ frames[i] = new MD5Joints();\r
+ frames[i].read(in);\r
+ }\r
+ int numBounds = in.readInt();\r
+ bounds = new BoundingBox[numBounds];\r
+ for(int i=0; i<numBounds; i++)\r
+ {\r
+ bounds[i] = new BoundingBox();\r
+ bounds[i].min.x = in.readFloat();\r
+ bounds[i].min.y = in.readFloat();\r
+ bounds[i].min.z = in.readFloat();\r
+ bounds[i].max.x = in.readFloat();\r
+ bounds[i].max.y = in.readFloat();\r
+ bounds[i].max.z = in.readFloat();\r
+ }\r
+ }\r
+\r
+ \r
static MD5Quaternion jointAOrient = new MD5Quaternion();\r
static MD5Quaternion jointBOrient = new MD5Quaternion();\r
\r
* Sets the currently playing {@link MD5Animation}.\r
* @param anim\r
* An {@link MD5Animation}.\r
- * @param loop\r
- * Whether to loop the animation.\r
+ * @param WrapMode\r
+ * The animation {@link WrapMode}.\r
*/\r
- public void setAnimation(Animation anim, boolean loop)\r
+ public void setAnimation(Animation anim, WrapMode wrapMode)\r
{\r
- super.setAnimation(anim, loop);\r
+ super.setAnimation(anim, wrapMode);\r
\r
if(anim != null)\r
{\r
\r
package com.badlogic.gdx.graphics.g3d.loaders.md5;\r
\r
+import java.io.DataInputStream;\r
+import java.io.DataOutputStream;\r
+import java.io.IOException;\r
+\r
public class MD5Joints {\r
public String[] names;\r
public int numJoints;\r
/** (0) parent, (1) pos.x, (2) pos.y, (3) pos.z, (4) orient.x, (5) orient.y, (6) orient.z, (7) orient.w **/\r
+ private static final int stride = 8;\r
public float[] joints;\r
+ \r
+ public void read(DataInputStream in) throws IOException {\r
+ int numNames = in.readInt();\r
+ names = new String[numNames];\r
+ for(int i=0; i<numNames; i++)\r
+ {\r
+ names[i] = in.readUTF();\r
+ }\r
+ numJoints = in.readInt();\r
+ joints = new float[numJoints*stride];\r
+ for(int i=0; i<numJoints*stride; i++)\r
+ {\r
+ joints[i] = in.readFloat();\r
+ }\r
+ }\r
+ \r
+ public void write(DataOutputStream out) throws IOException {\r
+ out.writeInt(names.length);\r
+ for(int i=0; i<names.length; i++)\r
+ {\r
+ out.writeUTF(names[i]);\r
+ }\r
+ out.writeInt(numJoints);\r
+ for(int i=0; i<numJoints*stride; i++)\r
+ {\r
+ out.writeFloat(joints[i]);\r
+ }\r
+ }\r
}\r
return null;\r
} \r
}\r
-\r
+ \r
public static MD5Animation loadAnimation (InputStream in) {\r
BufferedReader reader = new BufferedReader(new InputStreamReader(in));\r
List<String> tokens = new ArrayList<String>();\r
\r
package com.badlogic.gdx.graphics.g3d.loaders.md5;\r
\r
+import java.io.DataInputStream;\r
+import java.io.DataOutputStream;\r
+import java.io.IOException;\r
+\r
import com.badlogic.gdx.math.Vector3;\r
import com.badlogic.gdx.math.collision.BoundingBox;\r
\r
}\r
}\r
\r
+ public void read(DataInputStream in) throws IOException {\r
+ shader = in.readUTF();\r
+ numVertices = in.readInt();\r
+ numWeights = in.readInt();\r
+ numTriangles = in.readInt();\r
+ floatsPerVertex = in.readInt();\r
+ floatsPerWeight = in.readInt();\r
+ \r
+ vertices = new float[numVertices*floatsPerVertex];\r
+ indices = new short[numTriangles*3];\r
+ weights = new float[numWeights*floatsPerWeight];\r
+ for(int i=0; i<vertices.length; i++)\r
+ {\r
+ vertices[i] = in.readFloat();\r
+ }\r
+ for(int i=0; i<indices.length; i++)\r
+ {\r
+ indices[i] = in.readShort();\r
+ }\r
+ for(int i=0; i<weights.length; i++)\r
+ {\r
+ weights[i] = in.readFloat();\r
+ }\r
+ }\r
+\r
+ public void write(DataOutputStream out) throws IOException {\r
+ out.writeUTF(shader);\r
+ out.writeInt(numVertices);\r
+ out.writeInt(numWeights);\r
+ out.writeInt(numTriangles);\r
+ out.writeInt(floatsPerVertex);\r
+ out.writeInt(floatsPerWeight);\r
+ for(int i=0; i<vertices.length; i++)\r
+ {\r
+ out.writeFloat(vertices[i]);\r
+ }\r
+ for(int i=0; i<indices.length; i++)\r
+ {\r
+ out.writeShort(indices[i]);\r
+ }\r
+ for(int i=0; i<weights.length; i++)\r
+ {\r
+ out.writeFloat(weights[i]);\r
+ }\r
+ }\r
}\r
\r
package com.badlogic.gdx.graphics.g3d.loaders.md5;\r
\r
+import java.io.DataInputStream;\r
+import java.io.DataOutputStream;\r
+import java.io.IOException;\r
+\r
/**\r
* Represents an MD5 (Doom 3) skinned model.\r
* Note: The normal interpolation implementation is experimental. Using it will incur a greater CPU overhead, and correct normals\r
\r
return numTriangles;\r
}\r
+ \r
+ public void read(DataInputStream in) throws IOException\r
+ {\r
+ numJoints = in.readInt();\r
+ baseSkeleton = new MD5Joints();\r
+ baseSkeleton.read(in);\r
+ int numMeshes = in.readInt();\r
+ meshes = new MD5Mesh[numMeshes];\r
+ for(int i=0; i<numMeshes; i++)\r
+ {\r
+ meshes[i] = new MD5Mesh();\r
+ meshes[i].read(in);\r
+ }\r
+ }\r
+ \r
+ public void write(DataOutputStream out) throws IOException\r
+ {\r
+ out.writeInt(numJoints);\r
+ baseSkeleton.write(out);\r
+ out.writeInt(meshes.length);\r
+ for(int i=0; i<meshes.length; i++)\r
+ {\r
+ meshes[i].write(out);\r
+ }\r
+ }\r
}\r