OSDN Git Service

dc1ce5e4e0ad173856f66728ff157126f38a114d
[mikumikustudio/MikuMikuStudio.git] / src / com / jme / animation / AnimationController.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.jme.animation;
34
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.Set;
38
39 import com.jme.math.FastMath;
40 import com.jme.scene.Controller;
41 import com.jme.scene.Spatial;
42 import com.jme.util.export.InputCapsule;
43 import com.jme.util.export.JMEExporter;
44 import com.jme.util.export.JMEImporter;
45 import com.jme.util.export.OutputCapsule;
46 import com.jme.util.export.Savable;
47
48 /**
49  * AnimationController provides a method for managing multiple BoneAnimations.
50  * AnimationController maintains a list of available animations to play, and
51  * a reference to the currently active animation. The currently active animation
52  * can be set via index into the animation set, the name of the animation or
53  * a reference to the animation itself. If blending is used, the active animation
54  * is not immediately switched, but instead morphs with an incoming animation via
55  * crossfading for a specified period of time. When the blend is complete, the
56  * active animation is set to the incoming animation.
57  * 
58  * The 
59  * 
60  * @see com.jme.animation.BoneAnimation
61  * @author mpowell
62  *
63  */
64 public class AnimationController extends Controller implements Savable {
65     private static final long serialVersionUID = 1L;
66
67     private ArrayList<BoneAnimation> animationSets;
68
69     private Bone skeleton;
70     
71     private Spatial modelNode;
72
73     private BoneAnimation activeAnimation;
74     
75 //    private class ModifierData {
76 //        public BoneAnimation animation;
77 //        public float blendTime;
78 //    }
79 //    private ArrayList<ModifierData> activeAnimationsList = new ArrayList<ModifierData>();
80
81     private BoneAnimation blendAnimation;
82
83     private float currentBlendTime;
84
85     private float endBlendTime;
86
87     private boolean needsSync;
88
89     private int previousTest = -1;
90
91     private AnimationProperties props;
92     
93     public AnimationController() {
94     }
95
96     public void addAnimation(BoneAnimation bac) {
97         if (bac == null) {
98             return;
99         }
100
101         if (animationSets == null) {
102             animationSets = new ArrayList<BoneAnimation>();
103         }
104         animationSets.add(bac);
105
106         if (skeleton != null) {
107             bac.assignSkeleton(skeleton);
108         }
109     }
110     
111     public boolean hasAnimation(String name) {
112         if (animationSets != null) {
113             for (int i = 0; i < animationSets.size(); i++) {
114                 if (animationSets.get(i).getName().equalsIgnoreCase(name)) {
115                     return true;
116                 }
117             }
118         }
119         
120         return false;
121     }
122     
123     public BoneAnimation getAnimation(String name) {
124         if (animationSets != null) {
125             for (int i = 0; i < animationSets.size(); i++) {
126                 if (animationSets.get(i).getName().equalsIgnoreCase(name)) {
127                     return animationSets.get(i);
128                 }
129             }
130         }
131         
132         return null;
133     }
134
135     public void removeAnimation(BoneAnimation bac) {
136         if (animationSets != null) {
137             animationSets.remove(bac);
138
139             if (animationSets.size() == 0) {
140                 activeAnimation = null;
141             } else if (bac == activeAnimation) {
142                 activeAnimation = animationSets.get(0);
143             }
144         }
145
146     }
147
148     public void removeAnimation(int index) {
149         if (animationSets == null || index < 0 || index >= animationSets.size()) {
150             return;
151         }
152
153         BoneAnimation bac = animationSets.get(index);
154         animationSets.remove(index);
155         if (animationSets.size() == 0) {
156             activeAnimation = null;
157         } else if (bac == activeAnimation) {
158             activeAnimation = animationSets.get(0);
159         }
160
161     }
162
163     public BoneAnimation getActiveAnimation() {
164         return activeAnimation;
165     }
166
167     public void setCurrentFrame(int frame) {
168         if (activeAnimation != null) {
169             activeAnimation.setCurrentFrame(frame);
170         }
171     }
172
173     public void clearAnimations() {
174         if (animationSets != null) {
175             animationSets.clear();
176         }
177     }
178
179     public BoneAnimation getAnimation(int i) {
180         if (animationSets != null) {
181             return animationSets.get(i);
182         } else {
183             return null;
184         }
185     }
186
187     public ArrayList<BoneAnimation> getAnimations() {
188         return animationSets;
189     }
190
191     public void clearActiveAnimation() {
192         activeAnimation = null;
193         blendAnimation = null;
194     }
195
196     public void setBlendAnimation(BoneAnimation blendAnimation,
197             float blendTime, boolean sync) {
198         
199         // don't blend between the same animation
200         if (blendAnimation == this.blendAnimation) {
201             return;
202         }
203
204         if(blendAnimation == this.activeAnimation /*&& this.blendAnimation == null*/) {
205             needsSync = false;
206             this.blendAnimation = blendAnimation;
207             this.blendAnimation.setCurrentFrame(this.activeAnimation.getCurrentFrame());
208             currentBlendTime = 1;
209         } else {
210 //            if (this.blendAnimation != null) {
211 //                ModifierData modifierData = new ModifierData();
212 //                modifierData.animation = this.blendAnimation;
213 //                modifierData.blendTime = this.currentBlendTime;
214 //                activeAnimationsList.add(modifierData);
215 //            }
216
217             blendAnimation.reset();            
218
219             this.blendAnimation = blendAnimation;
220     
221             if (sync) {
222                 needsSync = true;
223                 calculateSyncFrame();
224             } else {
225                 needsSync = false;
226             }
227             
228             currentBlendTime = 0f;
229             endBlendTime = blendTime;
230         }
231     }
232
233     private void calculateSyncFrame() {
234         if (activeAnimation != null && blendAnimation != null
235                 && previousTest != activeAnimation.getCurrentFrame()) {
236
237             previousTest = activeAnimation.getCurrentFrame();
238
239             // Make sure current animation has sync tags.
240             Set<String> activeTags = activeAnimation.getAllSyncTags();
241             if (activeTags == null || activeTags.size() == 0) {
242                 needsSync = false;
243                 return;
244             }
245
246             // Make sure blend animation has sync tags.
247             Set<String> blendTags = blendAnimation.getAllSyncTags();
248             if (blendTags == null || blendTags.size() == 0) {
249                 needsSync = false;
250                 return;
251             }
252
253             // Make sure there is at least one matching sync tag to use
254             boolean match = false;
255             for (String name : activeTags) {
256                 if (blendTags.contains(name)) {
257                     match = true;
258                     break;
259                 }
260             }
261
262             if (!match) {
263                 needsSync = false;
264                 return;
265             }
266
267             // We can sync, so let's try.
268             ArrayList<String> currentSyncTags = activeAnimation
269                     .getSyncNames(previousTest);
270
271             // We can't sync yet, wait a bit.
272             if (currentSyncTags.size() == 0) {
273                 return;
274             }
275
276             for (String tag : currentSyncTags) {
277                 if (blendTags.contains(tag)) {
278                     // we can sync here
279                     int[] frames = blendAnimation.getSyncFrames(tag);
280                     // find the closest match to our frame.
281                     int old = (int) FastMath.abs(frames[0] - previousTest);
282                     int diff = 0;
283                     int i = 1;
284                     for (i = 1; i < frames.length; i++) {
285                         diff = (int) FastMath.abs(frames[i] - previousTest);
286                         if (diff > old) {
287                             // old is the sync frame
288                             break;
289                         }
290                     }
291                     blendAnimation.setInitialFrame(frames[i - 1]);
292                     needsSync = false;
293                     return;
294                 }
295             }
296
297             // TODO: If we made it this far, we need to wait a few more frames
298             // before
299             // syncing. This can be precalculated so that we don't do this each
300             // frame.
301         }
302     }
303     
304     public void updateProps() {
305         if(props.isAllowTranslation() && skeleton != null && modelNode != null) {
306             if(blendAnimation != null) {
307                 blendAnimation.setSourceBone(skeleton);
308                 blendAnimation.setDestSpatial(modelNode);
309                 blendAnimation.setAnimationProperties(props);
310                 
311             }
312             
313             if(activeAnimation != null) {
314                 activeAnimation.setSourceBone(skeleton);
315                 activeAnimation.setDestSpatial(modelNode);
316                 activeAnimation.setAnimationProperties(props);
317                 
318             }
319         } else {
320             if(blendAnimation != null) {
321                 blendAnimation.setSourceBone(null);
322                 blendAnimation.setDestSpatial(null);
323             }
324             
325             if(activeAnimation != null) {
326                 activeAnimation.setSourceBone(null);
327                 activeAnimation.setDestSpatial(null);
328             }
329         }
330     }
331
332     public void setActiveAnimation(String name) {
333         setActiveAnimation(name, false, 0, null);
334     }
335     
336     public void setActiveAnimation(String name, boolean blend, float time,
337             AnimationProperties props) {
338         this.props = props;
339         if (blend) {
340             if (animationSets != null) {
341                 for (int i = 0; i < animationSets.size(); i++) {
342                     if (animationSets.get(i).getName().equalsIgnoreCase(name)) {
343                         setBlendAnimation(animationSets.get(i), time, !props.isOneOff());
344                         return;
345                     }
346                 }
347             }
348         } else {
349             BoneAnimation old = activeAnimation;
350             if (animationSets != null) {
351                 for (int i = 0; i < animationSets.size(); i++) {
352                     if (animationSets.get(i).getName().equalsIgnoreCase(name)) {
353                         activeAnimation = animationSets.get(i);
354                         if (old != activeAnimation) {
355                             this.blendAnimation = null;
356                         }
357                         return;
358                     }
359                 }
360             }
361
362             // Invalid animation, set active to null
363             clearActiveAnimation();
364         }
365     }
366
367     public void setActiveAnimation(BoneAnimation bac) {
368         setActiveAnimation(bac, false, 0, false);
369     }
370
371     public void setActiveAnimation(BoneAnimation bac, boolean blend,
372             float time, boolean sync) {
373         if (blend) {
374             setBlendAnimation(bac, time, sync);
375             return;
376         } else {
377             BoneAnimation old = activeAnimation;
378             if (animationSets != null) {
379                 for (int i = 0; i < animationSets.size(); i++) {
380                     if (animationSets.get(i) == bac) {
381                         activeAnimation = animationSets.get(i);
382                         if (old != activeAnimation) {
383                             this.blendAnimation = null;
384                         }
385                         return;
386                     }
387                 }
388             }
389             // Invalid animation, set active to null
390             clearActiveAnimation();
391         }
392     }
393
394     public void setActiveAnimation(int index) {
395         setActiveAnimation(index, false, 0, false);
396     }
397
398     public void setActiveAnimation(int index, boolean blend, float time,
399             boolean sync) {
400         if (blend) {
401             if (animationSets != null && animationSets.size() > index) {
402                 setBlendAnimation(animationSets.get(index), time, sync);
403                 return;
404             }
405         } else {
406             BoneAnimation old = activeAnimation;
407             if (animationSets != null && animationSets.size() > index) {
408                 activeAnimation = animationSets.get(index);
409                 if (old != activeAnimation) {
410                     this.blendAnimation = null;
411                 }
412                 return;
413             }
414             // Invalid animation, set active to null
415             clearActiveAnimation();
416         }
417     }
418
419     public void setSkeleton(Bone b) {
420         this.skeleton = b;
421         if (animationSets != null) {
422             for (int i = 0; i < animationSets.size(); i++) {
423                 animationSets.get(i).assignSkeleton(skeleton);
424             }
425         }
426     }
427
428     @Override
429     public void update(float time) {
430         //We are blending into nothing, so just set this as active.
431         if(blendAnimation != null && activeAnimation == null) {
432                 activeAnimation = blendAnimation;
433                 blendAnimation = null;
434         }
435         
436         if (blendAnimation != null && !needsSync) {
437             currentBlendTime += time / endBlendTime;
438             if (currentBlendTime >= 1.0f) {
439 //                activeAnimationsList.clear();
440                 
441                 activeAnimation = blendAnimation;
442                 blendAnimation = null;
443                 updateProps();
444             }
445         }
446
447         if (activeAnimation != null) {
448             activeAnimation.update(time, getRepeatType(), getSpeed());
449 //            for (ModifierData modifierData : activeAnimationsList) {
450 //                modifierData.animation.update(time, getRepeatType(), getSpeed(),
451 //                        modifierData.blendTime);                
452 //            }
453
454             if (blendAnimation != null) {
455                 if (!needsSync) {
456                     blendAnimation.update(time, getRepeatType(), getSpeed(),
457                             currentBlendTime);
458                 } else {
459                     calculateSyncFrame();
460                 }
461             }
462         }
463     }
464
465     public void write(JMEExporter e) throws IOException {
466         super.write(e);
467         OutputCapsule cap = e.getCapsule(this);
468         cap.writeSavableArrayList(animationSets, "animationSets", null);
469         cap.write(skeleton, "skeleton", null);
470         cap.write(activeAnimation, "activeAnimation", null);
471     }
472
473     @SuppressWarnings("unchecked")
474     public void read(JMEImporter e) throws IOException {
475         super.read(e);
476         InputCapsule cap = e.getCapsule(this);
477         animationSets = cap.readSavableArrayList("animationSets", null);
478         skeleton = (Bone) cap.readSavable("skeleton", null);
479         activeAnimation = (BoneAnimation) cap.readSavable("activeAnimation",
480                 null);
481     }
482
483     public BoneAnimation getBlendAnimation() {
484         return blendAnimation;
485     }
486
487     public Spatial getModelNode() {
488         return modelNode;
489     }
490
491     public void setModelNode(Spatial modelNode) {
492         this.modelNode = modelNode;
493     }
494
495     public AnimationProperties getProps() {
496         return props;
497     }
498
499     public void setProps(AnimationProperties props) {
500         this.props = props;
501     }
502 }