1 /*
2  * Copyright (c) 2009-2010 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 package com.jme3.animation;
33 
34 import com.jme3.export.*;
35 import com.jme3.renderer.RenderManager;
36 import com.jme3.renderer.ViewPort;
37 import com.jme3.scene.Mesh;
38 import com.jme3.scene.Spatial;
39 import com.jme3.scene.control.AbstractControl;
40 import com.jme3.scene.control.Control;
41 import com.jme3.util.TempVars;
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.Collection;
45 import java.util.HashMap;
46 
47 /**
48  * <code>AnimControl</code> is a Spatial control that allows manipulation
49  * of skeletal animation.
50  *
51  * The control currently supports:
52  * 1) Animation blending/transitions
53  * 2) Multiple animation channels
54  * 3) Multiple skins
55  * 4) Animation event listeners
56  * 5) Animated model cloning
57  * 6) Animated model binary import/export
58  *
59  * Planned:
60  * 1) Hardware skinning
61  * 2) Morph/Pose animation
62  * 3) Attachments
63  * 4) Add/remove skins
64  *
65  * @author Kirill Vainer
66  */
67 public final class AnimControl extends AbstractControl implements Cloneable {
68 
69     /**
70      * Skeleton object must contain corresponding data for the targets' weight buffers.
71      */
72     Skeleton skeleton;
73     /** only used for backward compatibility */
74     @Deprecated
75     private SkeletonControl skeletonControl;
76     /**
77      * List of animations
78      */
79     HashMap<String, Animation> animationMap;
80     /**
81      * Animation channels
82      */
83     private transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>();
84     /**
85      * Animation event listeners
86      */
87     private transient ArrayList<AnimEventListener> listeners = new ArrayList<AnimEventListener>();
88 
89     /**
90      * Creates a new animation control for the given skeleton.
91      * The method {@link AnimControl#setAnimations(java.util.HashMap) }
92      * must be called after initialization in order for this class to be useful.
93      *
94      * @param skeleton The skeleton to animate
95      */
AnimControl(Skeleton skeleton)96     public AnimControl(Skeleton skeleton) {
97         this.skeleton = skeleton;
98         reset();
99     }
100 
101     /**
102      * Serialization only. Do not use.
103      */
AnimControl()104     public AnimControl() {
105     }
106 
107     /**
108      * Internal use only.
109      */
cloneForSpatial(Spatial spatial)110     public Control cloneForSpatial(Spatial spatial) {
111         try {
112             AnimControl clone = (AnimControl) super.clone();
113             clone.spatial = spatial;
114             clone.channels = new ArrayList<AnimChannel>();
115             clone.listeners = new ArrayList<AnimEventListener>();
116 
117             if (skeleton != null) {
118                 clone.skeleton = new Skeleton(skeleton);
119             }
120 
121             // animationMap is reference-copied, animation data should be shared
122             // to reduce memory usage.
123 
124             return clone;
125         } catch (CloneNotSupportedException ex) {
126             throw new AssertionError();
127         }
128     }
129 
130     /**
131      * @param animations Set the animations that this <code>AnimControl</code>
132      * will be capable of playing. The animations should be compatible
133      * with the skeleton given in the constructor.
134      */
setAnimations(HashMap<String, Animation> animations)135     public void setAnimations(HashMap<String, Animation> animations) {
136         animationMap = animations;
137     }
138 
139     /**
140      * Retrieve an animation from the list of animations.
141      * @param name The name of the animation to retrieve.
142      * @return The animation corresponding to the given name, or null, if no
143      * such named animation exists.
144      */
getAnim(String name)145     public Animation getAnim(String name) {
146         if (animationMap == null) {
147             animationMap = new HashMap<String, Animation>();
148         }
149         return animationMap.get(name);
150     }
151 
152     /**
153      * Adds an animation to be available for playing to this
154      * <code>AnimControl</code>.
155      * @param anim The animation to add.
156      */
addAnim(Animation anim)157     public void addAnim(Animation anim) {
158         if (animationMap == null) {
159             animationMap = new HashMap<String, Animation>();
160         }
161         animationMap.put(anim.getName(), anim);
162     }
163 
164     /**
165      * Remove an animation so that it is no longer available for playing.
166      * @param anim The animation to remove.
167      */
removeAnim(Animation anim)168     public void removeAnim(Animation anim) {
169         if (!animationMap.containsKey(anim.getName())) {
170             throw new IllegalArgumentException("Given animation does not exist "
171                     + "in this AnimControl");
172         }
173 
174         animationMap.remove(anim.getName());
175     }
176 
177     /**
178      * Create a new animation channel, by default assigned to all bones
179      * in the skeleton.
180      *
181      * @return A new animation channel for this <code>AnimControl</code>.
182      */
createChannel()183     public AnimChannel createChannel() {
184         AnimChannel channel = new AnimChannel(this);
185         channels.add(channel);
186         return channel;
187     }
188 
189     /**
190      * Return the animation channel at the given index.
191      * @param index The index, starting at 0, to retrieve the <code>AnimChannel</code>.
192      * @return The animation channel at the given index, or throws an exception
193      * if the index is out of bounds.
194      *
195      * @throws IndexOutOfBoundsException If no channel exists at the given index.
196      */
getChannel(int index)197     public AnimChannel getChannel(int index) {
198         return channels.get(index);
199     }
200 
201     /**
202      * @return The number of channels that are controlled by this
203      * <code>AnimControl</code>.
204      *
205      * @see AnimControl#createChannel()
206      */
getNumChannels()207     public int getNumChannels() {
208         return channels.size();
209     }
210 
211     /**
212      * Clears all the channels that were created.
213      *
214      * @see AnimControl#createChannel()
215      */
clearChannels()216     public void clearChannels() {
217         channels.clear();
218     }
219 
220     /**
221      * @return The skeleton of this <code>AnimControl</code>.
222      */
getSkeleton()223     public Skeleton getSkeleton() {
224         return skeleton;
225     }
226 
227     /**
228      * Adds a new listener to receive animation related events.
229      * @param listener The listener to add.
230      */
addListener(AnimEventListener listener)231     public void addListener(AnimEventListener listener) {
232         if (listeners.contains(listener)) {
233             throw new IllegalArgumentException("The given listener is already "
234                     + "registed at this AnimControl");
235         }
236 
237         listeners.add(listener);
238     }
239 
240     /**
241      * Removes the given listener from listening to events.
242      * @param listener
243      * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
244      */
removeListener(AnimEventListener listener)245     public void removeListener(AnimEventListener listener) {
246         if (!listeners.remove(listener)) {
247             throw new IllegalArgumentException("The given listener is not "
248                     + "registed at this AnimControl");
249         }
250     }
251 
252     /**
253      * Clears all the listeners added to this <code>AnimControl</code>
254      *
255      * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
256      */
clearListeners()257     public void clearListeners() {
258         listeners.clear();
259     }
260 
notifyAnimChange(AnimChannel channel, String name)261     void notifyAnimChange(AnimChannel channel, String name) {
262         for (int i = 0; i < listeners.size(); i++) {
263             listeners.get(i).onAnimChange(this, channel, name);
264         }
265     }
266 
notifyAnimCycleDone(AnimChannel channel, String name)267     void notifyAnimCycleDone(AnimChannel channel, String name) {
268         for (int i = 0; i < listeners.size(); i++) {
269             listeners.get(i).onAnimCycleDone(this, channel, name);
270         }
271     }
272 
273     /**
274      * Internal use only.
275      */
276     @Override
setSpatial(Spatial spatial)277     public void setSpatial(Spatial spatial) {
278         if (spatial == null && skeletonControl != null) {
279             this.spatial.removeControl(skeletonControl);
280         }
281 
282         super.setSpatial(spatial);
283 
284         //Backward compatibility.
285         if (spatial != null && skeletonControl != null) {
286             spatial.addControl(skeletonControl);
287         }
288     }
289 
reset()290     final void reset() {
291         if (skeleton != null) {
292             skeleton.resetAndUpdate();
293         }
294     }
295 
296     /**
297      * @return The names of all animations that this <code>AnimControl</code>
298      * can play.
299      */
getAnimationNames()300     public Collection<String> getAnimationNames() {
301         return animationMap.keySet();
302     }
303 
304     /**
305      * Returns the length of the given named animation.
306      * @param name The name of the animation
307      * @return The length of time, in seconds, of the named animation.
308      */
getAnimationLength(String name)309     public float getAnimationLength(String name) {
310         Animation a = animationMap.get(name);
311         if (a == null) {
312             throw new IllegalArgumentException("The animation " + name
313                     + " does not exist in this AnimControl");
314         }
315 
316         return a.getLength();
317     }
318 
319     /**
320      * Internal use only.
321      */
322     @Override
controlUpdate(float tpf)323     protected void controlUpdate(float tpf) {
324         if (skeleton != null) {
325             skeleton.reset(); // reset skeleton to bind pose
326         }
327 
328         TempVars vars = TempVars.get();
329         for (int i = 0; i < channels.size(); i++) {
330             channels.get(i).update(tpf, vars);
331         }
332         vars.release();
333 
334         if (skeleton != null) {
335             skeleton.updateWorldVectors();
336         }
337     }
338 
339     /**
340      * Internal use only.
341      */
342     @Override
controlRender(RenderManager rm, ViewPort vp)343     protected void controlRender(RenderManager rm, ViewPort vp) {
344     }
345 
346     @Override
write(JmeExporter ex)347     public void write(JmeExporter ex) throws IOException {
348         super.write(ex);
349         OutputCapsule oc = ex.getCapsule(this);
350         oc.write(skeleton, "skeleton", null);
351         oc.writeStringSavableMap(animationMap, "animations", null);
352     }
353 
354     @Override
read(JmeImporter im)355     public void read(JmeImporter im) throws IOException {
356         super.read(im);
357         InputCapsule in = im.getCapsule(this);
358         skeleton = (Skeleton) in.readSavable("skeleton", null);
359         animationMap = (HashMap<String, Animation>) in.readStringSavableMap("animations", null);
360 
361         if (im.getFormatVersion() == 0) {
362             // Changed for backward compatibility with j3o files generated
363             // before the AnimControl/SkeletonControl split.
364 
365             // If we find a target mesh array the AnimControl creates the
366             // SkeletonControl for old files and add it to the spatial.
367             // When backward compatibility won't be needed anymore this can deleted
368             Savable[] sav = in.readSavableArray("targets", null);
369             if (sav != null) {
370                 Mesh[] targets = new Mesh[sav.length];
371                 System.arraycopy(sav, 0, targets, 0, sav.length);
372                 skeletonControl = new SkeletonControl(targets, skeleton);
373                 spatial.addControl(skeletonControl);
374             }
375         }
376     }
377 }
378