OSDN Git Service

[Fixed] Memory leak in KeyframedModel.dispose()
authordavedx@gmail.com <davedx@gmail.com@6c4fd544-2939-11df-bb46-9574ba5d0bfa>
Thu, 27 Jan 2011 12:27:21 +0000 (12:27 +0000)
committerdavedx@gmail.com <davedx@gmail.com@6c4fd544-2939-11df-bb46-9574ba5d0bfa>
Thu, 27 Jan 2011 12:27:21 +0000 (12:27 +0000)
[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.

gdx/src/com/badlogic/gdx/graphics/TextureDict.java
gdx/src/com/badlogic/gdx/graphics/g3d/Animator.java
gdx/src/com/badlogic/gdx/graphics/g3d/keyframed/KeyframeAnimation.java
gdx/src/com/badlogic/gdx/graphics/g3d/keyframed/KeyframeAnimator.java
gdx/src/com/badlogic/gdx/graphics/g3d/keyframed/KeyframedModel.java
gdx/src/com/badlogic/gdx/graphics/g3d/loaders/md5/MD5Animation.java
gdx/src/com/badlogic/gdx/graphics/g3d/loaders/md5/MD5Animator.java
gdx/src/com/badlogic/gdx/graphics/g3d/loaders/md5/MD5Joints.java
gdx/src/com/badlogic/gdx/graphics/g3d/loaders/md5/MD5Loader.java
gdx/src/com/badlogic/gdx/graphics/g3d/loaders/md5/MD5Mesh.java
gdx/src/com/badlogic/gdx/graphics/g3d/loaders/md5/MD5Model.java

index b80d2d1..7dc8e0b 100644 (file)
@@ -89,8 +89,12 @@ public class TextureDict {
        /**\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
index 37cd834..c742803 100644 (file)
@@ -22,23 +22,31 @@ package com.badlogic.gdx.graphics.g3d;
 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
@@ -47,7 +55,7 @@ public abstract class Animator {
                if(mCurrentAnim != null)\r
                {\r
                        mAnimLen = mCurrentAnim.getLength(); \r
-               }       \r
+               }\r
        }\r
        \r
        /**\r
@@ -58,6 +66,15 @@ public abstract class Animator {
        {\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
@@ -68,16 +85,19 @@ public abstract class Animator {
        {\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
@@ -94,13 +114,13 @@ public abstract class Animator {
                                }\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
index f3a96db..01f1138 100644 (file)
@@ -23,13 +23,15 @@ public class KeyframeAnimation extends Animation {
        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
index 2be4a6b..3f30c8e 100644 (file)
@@ -12,6 +12,7 @@
  */\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
@@ -26,7 +27,7 @@ import com.badlogic.gdx.math.Vector3;
 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
@@ -97,47 +98,53 @@ public class KeyframeAnimator extends Animator {
        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
@@ -150,6 +157,14 @@ public class KeyframeAnimator extends Animator {
                }\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
index 098c566..355aeb4 100644 (file)
@@ -12,8 +12,6 @@
  */\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
@@ -22,6 +20,7 @@ import com.badlogic.gdx.graphics.Mesh;
 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
@@ -56,16 +55,6 @@ public class KeyframedModel {
         */\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
@@ -110,7 +99,7 @@ public class KeyframedModel {
         * @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
@@ -137,14 +126,14 @@ public class KeyframedModel {
                }\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
@@ -236,6 +225,8 @@ public class KeyframedModel {
                {\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
@@ -252,13 +243,15 @@ public class KeyframedModel {
         * 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
@@ -287,6 +280,10 @@ public class KeyframedModel {
                        {\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
@@ -345,5 +342,13 @@ public class KeyframedModel {
                                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
index 4429a72..0317b17 100644 (file)
 \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
@@ -29,6 +33,55 @@ public class MD5Animation extends Animation {
        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
index 7347d0a..9518270 100644 (file)
@@ -47,12 +47,12 @@ public class MD5Animator extends Animator {
         * 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
index 8f22b88..da0df16 100644 (file)
 \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
index 3f64cf3..300d7db 100644 (file)
@@ -210,7 +210,7 @@ public class MD5Loader {
                        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
index cc081ef..3237e9a 100644 (file)
 \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
@@ -366,4 +370,49 @@ public class MD5Mesh {
                }\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
index 436f25f..9011796 100644 (file)
 \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
@@ -43,4 +47,29 @@ public class MD5Model {
 \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