OSDN Git Service

Set optimal mime types and executable settings.
[mikumikustudio/MikuMikuStudio.git] / src / com / jmex / model / animation / JointController.java
1 /*
2  * Copyright (c) 2003-2009 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 
17  *   may be used to endorse or promote products derived from this software 
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 package com.jmex.model.animation;
34
35 import java.io.IOException;
36 import java.nio.FloatBuffer;
37 import java.util.ArrayList;
38
39 import com.jme.math.Quaternion;
40 import com.jme.math.TransformMatrix;
41 import com.jme.math.Vector3f;
42 import com.jme.scene.Controller;
43 import com.jme.system.JmeException;
44 import com.jme.util.export.InputCapsule;
45 import com.jme.util.export.JMEExporter;
46 import com.jme.util.export.JMEImporter;
47 import com.jme.util.export.OutputCapsule;
48 import com.jme.util.export.Savable;
49 import com.jme.util.geom.BufferUtils;
50 import com.jmex.model.JointMesh;
51
52 /**
53  * Started Date: Jun 9, 2004 <br>
54  * 
55  * This controller animates a Node's JointMesh children acording to the joints
56  * stored inside <code>movementInfo</code>.
57  * 
58  * @author Jack Lindamood
59  */
60 public class JointController extends Controller {
61
62     private static final long serialVersionUID = 1L;
63
64     /**
65      * It is JointController's responsibility to keep changePoints sorted by
66      * <code>time</code> at all times.
67      */
68     public int numJoints;
69
70     /**
71      * movementInfo[i] contains a float value time and an array of
72      * TransformMatrix. At time <code>time</code> the joint i is at movement
73      * <code>jointChange[i]</code>
74      */
75     public ArrayList<PointInTime> movementInfo;
76
77     /**
78      * parentIndex contains a list of who's parent a joint is. -1 indicates a
79      * root joint with no parent
80      */
81     public int[] parentIndex;
82
83     /**
84      * Local refrence matrix that can determine a joint's position in space
85      * relative to its parent.
86      */
87     public TransformMatrix[] localRefMatrix;
88
89     /** Currently unused. */
90     public float FPS;
91
92     /**
93      * Array of all the meshes this controller should consider animating.
94      */
95     public ArrayList<JointMesh> movingMeshes;
96
97     /**
98      * This controller's internal current time.
99      */
100     private float curTime;
101
102     /**
103      * This controller's internal current PointInTime index.
104      */
105     private int curTimePoint;
106
107     /**
108      * Used internally, they are updated every update(float) call to tell points
109      * how to change.
110      */
111     private TransformMatrix[] jointMovements;
112
113     /**
114      * The inverse chain matrix of every joint. Calculated once with
115      * prosessController()
116      */
117     private TransformMatrix[] inverseChainMatrix;
118
119     // Internal worker classes
120     private final static Quaternion unSyncbeginAngle = new Quaternion();
121
122     private final static Vector3f unSyncbeginPos = new Vector3f();
123
124     private final static TransformMatrix tempUnSyncd = new TransformMatrix();
125
126     /**
127      * Tells update that it should be called every <code>skipRate</code>
128      * seconds
129      */
130     public float skipRate;
131
132     /**
133      * Used with skipRate internally.
134      */
135     private float currentSkip;
136
137     /** If true, the model's bounding volume will update every frame. */
138     private boolean updatePerFrame = true;
139
140     private boolean movingForward = true;
141
142     public JointController() {
143         curTime = 0;
144         curTimePoint = 1;
145     }
146     
147     /**
148      * Constructs a new JointController that will hold the given number of
149      * joints.
150      * 
151      * @param numJoints
152      *            The number of joints this jointController will have
153      */
154     public JointController(int numJoints) {
155         this.numJoints = numJoints;
156         parentIndex = new int[numJoints];
157         localRefMatrix = new TransformMatrix[numJoints];
158         movingMeshes = new ArrayList<JointMesh>();
159         jointMovements = new TransformMatrix[numJoints];
160         inverseChainMatrix = new TransformMatrix[numJoints];
161         for (int i = 0; i < numJoints; i++) {
162             localRefMatrix[i] = new TransformMatrix();
163             jointMovements[i] = new TransformMatrix();
164             inverseChainMatrix[i] = new TransformMatrix();
165         }
166         movementInfo = new ArrayList<PointInTime>();
167         curTime = 0;
168         curTimePoint = 1;
169         currentSkip = 0;
170         skipRate = .01f;
171     }
172     
173     public float getCurrentTime() {
174         return curTime;
175     }
176
177     /**
178      * Tells JointController that at time <code>time</code> the joint
179      * <code>jointNumber</code> will translate to x,y,z relative to its parent
180      * 
181      * @param jointNumber
182      *            Index of joint to affect
183      * @param time
184      *            Which time the joint will take these values
185      * @param x
186      *            Joint's x translation
187      * @param y
188      *            Joint's y translation
189      * @param z
190      *            Joint's z translation
191      */
192     public void setTranslation(int jointNumber, float time, float x, float y,
193             float z) {
194         findUpToTime(time).setTranslation(jointNumber, x, y, z);
195     }
196
197     /**
198      * Tells JointController that at time <code>time</code> the joint
199      * <code>jointNumber</code> will translate to x,y,z relative to its parent
200      * 
201      * @param jointNumber
202      *            Index of joint to affect
203      * @param time
204      *            Which time the joint will take these values
205      * @param trans
206      *            Joint's translation
207      *  
208      */
209     public void setTranslation(int jointNumber, float time, Vector3f trans) {
210         findUpToTime(time).setTranslation(jointNumber, trans);
211     }
212
213     /**
214      * Tells JointController that at time <code>time</code> the joint
215      * <code>jointNumber</code> will rotate acording to the euler angles x,y,z
216      * relative to its parent's rotation
217      * 
218      * @param jointNumber
219      *            Index of joint to affect
220      * @param time
221      *            Which time the joint will take these values
222      * @param x
223      *            Joint's x rotation
224      * @param y
225      *            Joint's y rotation
226      * @param z
227      *            Joint's z rotation
228      */
229     public void setRotation(int jointNumber, float time, float x, float y,
230             float z) {
231         findUpToTime(time).setRotation(jointNumber, x, y, z);
232     }
233
234     /**
235      * Tells JointController that at time <code>time</code> the joint
236      * <code>jointNumber</code> will rotate acording to
237      * <code>Quaternion</code>.
238      * 
239      * @param jointNumber
240      *            Index of joint to affect
241      * @param time
242      *            Which time the joint will take these values
243      * @param quaternion
244      *            The joint's new rotation
245      */
246     public void setRotation(int jointNumber, float time, Quaternion quaternion) {
247         findUpToTime(time).setRotation(jointNumber, quaternion);
248     }
249
250     /**
251      * Used with setRotation and setTranslation. This function finds a point in
252      * time for given time. If one doesn't exist then a new PointInTime is
253      * created and returned.
254      * 
255      * @param time
256      * @return The PointInTime at that given time, or a new one if none exist so
257      *         far.
258      */
259     private PointInTime findUpToTime(float time) {
260         int index = 0;
261         for (PointInTime point : movementInfo) {
262             float curTime = point.time;
263             if (curTime >= time) break;
264             index++;
265         }
266         PointInTime storedNext = null;
267         if (index == movementInfo.size()) {
268             storedNext = new PointInTime(numJoints);
269             movementInfo.add(storedNext);
270             storedNext.time = time;
271         } else {
272             if (movementInfo.get(index).time == time) {
273                 storedNext = movementInfo.get(index);
274             } else {
275                 storedNext = new PointInTime(numJoints);
276                 movementInfo.add(index, storedNext);
277                 storedNext.time = time;
278             }
279         }
280         return storedNext;
281     }
282
283     /**
284      * Updates the <code>movingMeshes</code> by updating their joints +=time
285      * 
286      * @param time
287      *            Time from last update
288      */
289     public void update(float time) {
290         if (numJoints == 0) return;
291         if (movingForward)
292             curTime += time * this.getSpeed();
293         else
294             curTime -= time * this.getSpeed();
295         currentSkip += time;
296         if (currentSkip >= skipRate) {
297             currentSkip = 0;
298         } else {
299             return;
300         }
301
302         setCurTimePoint();
303         PointInTime now = movementInfo.get(curTimePoint);
304         PointInTime then = movementInfo.get(curTimePoint - 1);
305
306         float delta = (curTime - then.time) / (now.time - then.time);
307         createJointTransforms(delta);
308         combineWithInverse();
309         updateData();
310     }
311
312     private void setCurTimePoint() {
313
314         int repeatType = getRepeatType();
315         // reset if too far
316         if (curTime > getMaxTime()) {
317             if (repeatType == Controller.RT_WRAP)
318                 curTime = getMinTime();
319             else if (repeatType == Controller.RT_CYCLE) {
320                 curTime = getMaxTime();
321                 movingForward = false;
322             } else if (repeatType == Controller.RT_CLAMP) {
323                 setActive(false);
324                 curTime = getMaxTime();
325             }
326         }
327
328         if (curTime < getMinTime()) {
329             if (repeatType == Controller.RT_WRAP)
330                 curTime = getMaxTime();
331             else if (repeatType == Controller.RT_CYCLE) {
332                 curTime = getMinTime();
333                 movingForward = true;
334             } else if (repeatType == Controller.RT_CLAMP) {
335                 setActive(false);
336                 curTime = getMinTime();
337             }
338         }
339
340         // if curTimePoint works then return
341         PointInTime p1 = movementInfo.get(curTimePoint);
342         PointInTime p2 = movementInfo.get(curTimePoint - 1);
343         if (curTime <= p1.time && curTime >= p2.time) return;
344         for (curTimePoint = 1; curTimePoint < movementInfo.size(); curTimePoint++) {
345             p1 = movementInfo.get(curTimePoint);
346             if (p1.time >= curTime) return;
347         }
348     }
349
350     /**
351      * Sets the frames the joint controller will animate from and to. The frames
352      * are dependant upon the FPS. Remember that the first frame starts at 1,
353      * <b>NOT </b>0.
354      * 
355      * @param start
356      *            The starting frame number.
357      * @param end
358      *            The ending frame number.
359      */
360     public void setTimes(int start, int end) {
361         if (start < 0
362                 || start > end
363                 || movementInfo.get(movementInfo.size() - 1).time < end
364                         / FPS) {
365             String message = "Malformed times: start="
366                     + start
367                     + " end="
368                     + end
369                     + " start limit: 0 End limit: "
370                     + movementInfo.get(movementInfo.size() - 1).time
371                     * FPS;
372             throw new JmeException(message);
373         }
374         setMinTime(start / FPS);
375         setMaxTime(end / FPS);
376         curTime = getMinTime();
377         curTimePoint = 1;
378         movingForward = true;
379     }
380
381     /**
382      * Used with update(float). <code>updateData</code> moves every normal and
383      * vertex acording to its jointIndex
384      */
385     private void updateData() {
386         for (int currentGroup = 0; currentGroup < movingMeshes.size(); currentGroup++) {
387             JointMesh updatingGroup = movingMeshes
388                     .get(currentGroup);
389             int currentBoneIndex;
390             FloatBuffer verts = updatingGroup.getVertexBuffer();
391             FloatBuffer normals = updatingGroup.getNormalBuffer();
392             int j;
393             for (j = 0; j < updatingGroup.jointIndex.length; j++) {
394                 currentBoneIndex = updatingGroup.jointIndex[j];
395                 if (currentBoneIndex == -1) continue;
396                 unSyncbeginPos.set(updatingGroup.originalVertex[j]);
397                 BufferUtils.setInBuffer(jointMovements[currentBoneIndex]
398                                                        .multPoint(unSyncbeginPos), verts, j);
399                 unSyncbeginPos.set(updatingGroup.originalNormal[j]);
400                 BufferUtils.setInBuffer(jointMovements[currentBoneIndex]
401                                                        .multNormal(unSyncbeginPos), normals, j);
402             }
403             if (j != 0) {
404                 if (updatePerFrame) updatingGroup.updateModelBound();
405             }
406         }
407     }
408
409     /**
410      * Used with update(float) to combine joints with their inverse to properly
411      * translate points.
412      */
413     private void combineWithInverse() {
414         for (int i = 0; i < numJoints; i++)
415             jointMovements[i].multLocal(inverseChainMatrix[i], unSyncbeginPos);
416     }
417
418     /**
419      * Processes a JointController by filling holes and creating inverse
420      * matrixes. Should only be called once per JointController object lifetime
421      */
422     public void processController() {
423         if (movementInfo.size() == 1) { // IE no times were added or only time 0
424             // was added
425             movementInfo.add(0, new PointInTime(0));
426         }
427         setMinTime(movementInfo.get(0).time);
428         setMaxTime(movementInfo.get(movementInfo.size() - 1).time);
429         curTime = getMinTime();
430         invertWithParents();
431         fillHoles();
432     }
433
434     /**
435      * If true, the model's bounding volume will be updated every frame. If
436      * false, it will not.
437      * 
438      * @param update
439      *            The new update model volume per frame value.
440      */
441     public void setModelUpdate(boolean update) {
442         updatePerFrame = update;
443     }
444
445     /**
446      * Returns true if the model's bounding volume is being updated every frame.
447      * 
448      * @return True if bounding volume is updating.
449      */
450     public boolean getModelUpdate() {
451         return updatePerFrame;
452     }
453
454     /**
455      * Inverts joints with their parents. Only called once per JointController
456      * lifetime during processing.
457      */
458     private void invertWithParents() {
459         for (int i = 0; i < numJoints; i++) {
460             inverseChainMatrix[i] = new TransformMatrix(localRefMatrix[i]);
461             inverseChainMatrix[i].inverse();
462             if (parentIndex[i] != -1)
463                     inverseChainMatrix[i].multLocal(
464                             inverseChainMatrix[parentIndex[i]], unSyncbeginPos);
465         }
466     }
467
468     /**
469      * Called with update to create the needed joint transforms for that point
470      * in time.
471      * 
472      * @param changeAmnt
473      *            The % diffrence (from 0-1) between two points in time
474      */
475     private void createJointTransforms(float changeAmnt) {
476         PointInTime now = movementInfo.get(curTimePoint);
477         PointInTime then = movementInfo.get(curTimePoint - 1);
478         for (int index = 0; index < numJoints; index++) {
479             int theParentIndex = parentIndex[index];
480
481             unSyncbeginAngle.set(then.jointRotation[index]);
482             unSyncbeginPos.set(then.jointTranslation[index]);
483
484             unSyncbeginAngle.slerp(now.jointRotation[index], changeAmnt);
485             unSyncbeginPos.interpolate(now.jointTranslation[index], changeAmnt);
486
487             tempUnSyncd.set(unSyncbeginAngle, unSyncbeginPos);
488             jointMovements[index].set(localRefMatrix[index]);
489             jointMovements[index].multLocal(tempUnSyncd, unSyncbeginPos);
490             if (theParentIndex != -1) {
491                 tempUnSyncd.set(jointMovements[index]);
492                 jointMovements[index].set(jointMovements[theParentIndex]);
493                 jointMovements[index].multLocal(tempUnSyncd, unSyncbeginPos);
494             }
495         }
496     }
497
498     /**
499      * Fills null rotations and translations for any joint at any point in time.
500      */
501     private void fillHoles() {
502         fillRots();
503         fillTrans();
504     }
505
506     /**
507      * Gives every PointInTime for every joint a valid rotation.
508      */
509     private void fillRots() {
510         for (int joint = 0; joint < numJoints; joint++) {
511             // 1) Find first non-null rotation of joint <code>joint</code>
512             int start;
513             for (start = 0; start < movementInfo.size(); start++) {
514                 if (movementInfo.get(start).jointRotation[joint] != null)
515                         break;
516             }
517             if (start == movementInfo.size()) { // if they are all null then
518                 // fill with identity
519                 for (int i = 0; i < movementInfo.size(); i++)
520                     movementInfo.get(i).jointRotation[joint] = new Quaternion();
521                 continue; // we're done with this joint so lets continue
522             }
523             if (start != 0) { // if there -are- null elements at the begining,
524                 // then fill with first non-null
525                 unSyncbeginAngle
526                         .set(movementInfo.get(start).jointRotation[joint]);
527                 for (int i = 0; i < start; i++)
528                     movementInfo.get(i).jointRotation[joint] = new Quaternion(
529                             unSyncbeginAngle);
530             }
531             int lastgood = start;
532             boolean allGood = true;
533             for (int i = start + 2; i < movementInfo.size(); i++) {
534                 if (movementInfo.get(i).jointRotation[joint] != null) {
535                     fillQuats(joint, lastgood, i); // fills gaps
536                     lastgood = i;
537                     allGood = false;
538                 }
539             }
540             if (!allGood && lastgood != movementInfo.size() - 1) { // fills tail
541                 movementInfo.get(movementInfo.size() - 1).jointRotation[joint] = new Quaternion(
542                         movementInfo.get(lastgood).jointRotation[joint]);
543                 fillQuats(joint, lastgood, movementInfo.size() - 1); // fills
544                 // tail
545             }
546         }
547     }
548
549     /**
550      * Gives every PointInTime for every joint a valid translation.
551      */
552     private void fillTrans() {
553         for (int joint = 0; joint < numJoints; joint++) {
554             // 1) Find first non-null translation of joint <code>joint</code>
555             int start;
556             for (start = 0; start < movementInfo.size(); start++) {
557                 if (movementInfo.get(start).jointTranslation[joint] != null)
558                         break;
559             }
560             if (start == movementInfo.size()) { // if they are all null then
561                 // fill with identity
562                 for (int i = 0; i < movementInfo.size(); i++)
563                     movementInfo.get(i).jointTranslation[joint] = new Vector3f(
564                             0, 0, 0);
565                 continue; // we're done with this joint so lets continue
566             }
567             if (start != 0) { // if there -are- null elements at the begining,
568                 // then fill with first non-null
569                 unSyncbeginPos
570                         .set(movementInfo.get(start).jointTranslation[joint]);
571                 for (int i = 0; i < start; i++)
572                     movementInfo.get(i).jointTranslation[joint] = new Vector3f(
573                             unSyncbeginPos);
574             }
575             int lastgood = start;
576             boolean allGood = true;
577             for (int i = start + 2; i < movementInfo.size(); i++) {
578                 if (movementInfo.get(i).jointTranslation[joint] != null) {
579                     fillPos(joint, lastgood, i); // fills gaps
580                     lastgood = i;
581                     allGood = false;
582                 }
583             }
584             if (!allGood && lastgood != movementInfo.size() - 1) { // fills tail
585                 movementInfo.get(movementInfo.size() - 1).jointTranslation[joint] = new Vector3f(
586                         movementInfo.get(lastgood).jointTranslation[joint]);
587                 fillPos(joint, lastgood, movementInfo.size() - 1); // fills tail
588             }
589         }
590     }
591
592     /**
593      * Interpolates missing quats that weren't specified to the JointController.
594      * 
595      * @param jointIndex
596      *            Index of the joint that has missing quats
597      * @param startRotIndex
598      *            Begining index of a valid non-null quat
599      * @param endRotIndex
600      *            Ending index of a valid non-null quat
601      */
602     private void fillQuats(int jointIndex, int startRotIndex, int endRotIndex) {
603         unSyncbeginAngle
604                 .set(movementInfo.get(startRotIndex).jointRotation[jointIndex]);
605         for (int i = startRotIndex + 1; i < endRotIndex; i++) {
606             movementInfo.get(i).jointRotation[jointIndex] = new Quaternion(
607                     unSyncbeginAngle);
608             movementInfo.get(i).jointRotation[jointIndex]
609                     .slerp(
610                             movementInfo.get(endRotIndex).jointRotation[jointIndex],
611                             ((float) i - startRotIndex)
612                                     / (endRotIndex - startRotIndex));
613         }
614     }
615
616     /**
617      * Interpolates missing vector that weren't specified to the
618      * JointController.
619      * 
620      * @param jointIndex
621      *            Index of the joint that has missing vector
622      * @param startPosIndex
623      *            Begining index of a valid non-null vector
624      * @param endPosIndex
625      *            Ending index of a valid non-null vector
626      */
627     private void fillPos(int jointIndex, int startPosIndex, int endPosIndex) {
628         unSyncbeginPos
629                 .set(movementInfo.get(startPosIndex).jointTranslation[jointIndex]);
630         for (int i = startPosIndex + 1; i < endPosIndex; i++) {
631             movementInfo.get(i).jointTranslation[jointIndex] = new Vector3f(
632                     unSyncbeginPos);
633             movementInfo.get(i).jointTranslation[jointIndex]
634                     .interpolate(
635                             movementInfo.get(endPosIndex).jointTranslation[jointIndex],
636                             ((float) i - startPosIndex)
637                                     / (endPosIndex - startPosIndex));
638         }
639     }
640
641     /**
642      * Adds a jointmesh for this JointController to consider animating.
643      * 
644      * @param child
645      *            Child JointMesh to consider
646      */
647     public void addJointMesh(JointMesh child) {
648         movingMeshes.add(child);
649     }
650
651     public void write(JMEExporter e) throws IOException {
652         super.write(e);
653         OutputCapsule capsule = e.getCapsule(this);
654         capsule.write(numJoints, "numJoints", 0);
655         capsule.writeSavableArrayList(movementInfo, "movementInfo", new ArrayList<PointInTime>());
656         capsule.write(parentIndex, "parentIndex", new int[numJoints]);
657         capsule.write(localRefMatrix, "localRefMatrix", new TransformMatrix[numJoints]);
658         capsule.write(FPS, "FPS", 0);
659         capsule.writeSavableArrayList(movingMeshes, "movingMeshes", new ArrayList<JointMesh>());
660         capsule.write(jointMovements, "jointMovements", new TransformMatrix[numJoints]);
661         capsule.write(skipRate, "skipRate", 0);
662         capsule.write(updatePerFrame, "updatePerFrame", false);
663         capsule.write(movingForward, "movingForward", false);
664     }
665     
666     @SuppressWarnings("unchecked")
667         public void read(JMEImporter e) throws IOException {
668         super.read(e);
669         InputCapsule capsule = e.getCapsule(this);
670         numJoints = capsule.readInt("numJoints", 0);
671         movementInfo = capsule.readSavableArrayList("movementInfo", new ArrayList<PointInTime>());
672         parentIndex = capsule.readIntArray("parentIndex", new int[numJoints]);
673         
674         Savable[] savs = capsule.readSavableArray("localRefMatrix", new TransformMatrix[numJoints]);
675         if (savs == null)
676             localRefMatrix = null;
677         else {
678             localRefMatrix = new TransformMatrix[savs.length];
679             for (int x = 0; x < savs.length; x++) {
680                 localRefMatrix[x] = (TransformMatrix)savs[x];
681             }
682         }
683         
684         savs = capsule.readSavableArray("jointMovements", new TransformMatrix[numJoints]);
685         if (savs == null)
686             jointMovements = null;
687         else {
688             jointMovements = new TransformMatrix[savs.length];
689             for (int x = 0; x < savs.length; x++) {
690                 jointMovements[x] = (TransformMatrix)savs[x];
691             }
692         }
693         
694         FPS = capsule.readFloat("FPS", 0);
695         movingMeshes = capsule.readSavableArrayList("movingMeshes", new ArrayList<JointMesh>());
696         skipRate = capsule.readFloat("skipRate", 0);
697         updatePerFrame = capsule.readBoolean("updatePerFrame", false);
698         movingForward = capsule.readBoolean("movingForward", false);
699         
700         
701         inverseChainMatrix = new TransformMatrix[numJoints];
702         for (int i = 0; i < numJoints; i++) {
703             inverseChainMatrix[i] = new TransformMatrix();
704         }
705         processController();
706     }
707 }