2 * Copyright (c) 2003-2009 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
33 package com.jme.animation;
35 import java.io.IOException;
36 import java.util.ArrayList;
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;
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.
60 * @see com.jme.animation.BoneAnimation
64 public class AnimationController extends Controller implements Savable {
65 private static final long serialVersionUID = 1L;
67 private ArrayList<BoneAnimation> animationSets;
69 private Bone skeleton;
71 private Spatial modelNode;
73 private BoneAnimation activeAnimation;
75 // private class ModifierData {
76 // public BoneAnimation animation;
77 // public float blendTime;
79 // private ArrayList<ModifierData> activeAnimationsList = new ArrayList<ModifierData>();
81 private BoneAnimation blendAnimation;
83 private float currentBlendTime;
85 private float endBlendTime;
87 private boolean needsSync;
89 private int previousTest = -1;
91 private AnimationProperties props;
93 public AnimationController() {
96 public void addAnimation(BoneAnimation bac) {
101 if (animationSets == null) {
102 animationSets = new ArrayList<BoneAnimation>();
104 animationSets.add(bac);
106 if (skeleton != null) {
107 bac.assignSkeleton(skeleton);
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)) {
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);
135 public void removeAnimation(BoneAnimation bac) {
136 if (animationSets != null) {
137 animationSets.remove(bac);
139 if (animationSets.size() == 0) {
140 activeAnimation = null;
141 } else if (bac == activeAnimation) {
142 activeAnimation = animationSets.get(0);
148 public void removeAnimation(int index) {
149 if (animationSets == null || index < 0 || index >= animationSets.size()) {
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);
163 public BoneAnimation getActiveAnimation() {
164 return activeAnimation;
167 public void setCurrentFrame(int frame) {
168 if (activeAnimation != null) {
169 activeAnimation.setCurrentFrame(frame);
173 public void clearAnimations() {
174 if (animationSets != null) {
175 animationSets.clear();
179 public BoneAnimation getAnimation(int i) {
180 if (animationSets != null) {
181 return animationSets.get(i);
187 public ArrayList<BoneAnimation> getAnimations() {
188 return animationSets;
191 public void clearActiveAnimation() {
192 activeAnimation = null;
193 blendAnimation = null;
196 public void setBlendAnimation(BoneAnimation blendAnimation,
197 float blendTime, boolean sync) {
199 // don't blend between the same animation
200 if (blendAnimation == this.blendAnimation) {
204 if(blendAnimation == this.activeAnimation /*&& this.blendAnimation == null*/) {
206 this.blendAnimation = blendAnimation;
207 this.blendAnimation.setCurrentFrame(this.activeAnimation.getCurrentFrame());
208 currentBlendTime = 1;
210 // if (this.blendAnimation != null) {
211 // ModifierData modifierData = new ModifierData();
212 // modifierData.animation = this.blendAnimation;
213 // modifierData.blendTime = this.currentBlendTime;
214 // activeAnimationsList.add(modifierData);
217 blendAnimation.reset();
219 this.blendAnimation = blendAnimation;
223 calculateSyncFrame();
228 currentBlendTime = 0f;
229 endBlendTime = blendTime;
233 private void calculateSyncFrame() {
234 if (activeAnimation != null && blendAnimation != null
235 && previousTest != activeAnimation.getCurrentFrame()) {
237 previousTest = activeAnimation.getCurrentFrame();
239 // Make sure current animation has sync tags.
240 Set<String> activeTags = activeAnimation.getAllSyncTags();
241 if (activeTags == null || activeTags.size() == 0) {
246 // Make sure blend animation has sync tags.
247 Set<String> blendTags = blendAnimation.getAllSyncTags();
248 if (blendTags == null || blendTags.size() == 0) {
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)) {
267 // We can sync, so let's try.
268 ArrayList<String> currentSyncTags = activeAnimation
269 .getSyncNames(previousTest);
271 // We can't sync yet, wait a bit.
272 if (currentSyncTags.size() == 0) {
276 for (String tag : currentSyncTags) {
277 if (blendTags.contains(tag)) {
279 int[] frames = blendAnimation.getSyncFrames(tag);
280 // find the closest match to our frame.
281 int old = (int) FastMath.abs(frames[0] - previousTest);
284 for (i = 1; i < frames.length; i++) {
285 diff = (int) FastMath.abs(frames[i] - previousTest);
287 // old is the sync frame
291 blendAnimation.setInitialFrame(frames[i - 1]);
297 // TODO: If we made it this far, we need to wait a few more frames
299 // syncing. This can be precalculated so that we don't do this each
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);
313 if(activeAnimation != null) {
314 activeAnimation.setSourceBone(skeleton);
315 activeAnimation.setDestSpatial(modelNode);
316 activeAnimation.setAnimationProperties(props);
320 if(blendAnimation != null) {
321 blendAnimation.setSourceBone(null);
322 blendAnimation.setDestSpatial(null);
325 if(activeAnimation != null) {
326 activeAnimation.setSourceBone(null);
327 activeAnimation.setDestSpatial(null);
332 public void setActiveAnimation(String name) {
333 setActiveAnimation(name, false, 0, null);
336 public void setActiveAnimation(String name, boolean blend, float time,
337 AnimationProperties props) {
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());
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;
362 // Invalid animation, set active to null
363 clearActiveAnimation();
367 public void setActiveAnimation(BoneAnimation bac) {
368 setActiveAnimation(bac, false, 0, false);
371 public void setActiveAnimation(BoneAnimation bac, boolean blend,
372 float time, boolean sync) {
374 setBlendAnimation(bac, time, sync);
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;
389 // Invalid animation, set active to null
390 clearActiveAnimation();
394 public void setActiveAnimation(int index) {
395 setActiveAnimation(index, false, 0, false);
398 public void setActiveAnimation(int index, boolean blend, float time,
401 if (animationSets != null && animationSets.size() > index) {
402 setBlendAnimation(animationSets.get(index), time, sync);
406 BoneAnimation old = activeAnimation;
407 if (animationSets != null && animationSets.size() > index) {
408 activeAnimation = animationSets.get(index);
409 if (old != activeAnimation) {
410 this.blendAnimation = null;
414 // Invalid animation, set active to null
415 clearActiveAnimation();
419 public void setSkeleton(Bone b) {
421 if (animationSets != null) {
422 for (int i = 0; i < animationSets.size(); i++) {
423 animationSets.get(i).assignSkeleton(skeleton);
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;
436 if (blendAnimation != null && !needsSync) {
437 currentBlendTime += time / endBlendTime;
438 if (currentBlendTime >= 1.0f) {
439 // activeAnimationsList.clear();
441 activeAnimation = blendAnimation;
442 blendAnimation = null;
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);
454 if (blendAnimation != null) {
456 blendAnimation.update(time, getRepeatType(), getSpeed(),
459 calculateSyncFrame();
465 public void write(JMEExporter e) throws IOException {
467 OutputCapsule cap = e.getCapsule(this);
468 cap.writeSavableArrayList(animationSets, "animationSets", null);
469 cap.write(skeleton, "skeleton", null);
470 cap.write(activeAnimation, "activeAnimation", null);
473 @SuppressWarnings("unchecked")
474 public void read(JMEImporter e) throws IOException {
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",
483 public BoneAnimation getBlendAnimation() {
484 return blendAnimation;
487 public Spatial getModelNode() {
491 public void setModelNode(Spatial modelNode) {
492 this.modelNode = modelNode;
495 public AnimationProperties getProps() {
499 public void setProps(AnimationProperties props) {