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 */ 96 public AnimControl(Skeleton skeleton) { 97 this.skeleton = skeleton; 98 reset(); 99 } 100 101 /** 102 * Serialization only. Do not use. 103 */ 104 public AnimControl() { 105 } 106 107 /** 108 * Internal use only. 109 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 216 public void clearChannels() { 217 channels.clear(); 218 } 219 220 /** 221 * @return The skeleton of this <code>AnimControl</code>. 222 */ 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 */ 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 */ 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 */ 257 public void clearListeners() { 258 listeners.clear(); 259 } 260 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 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 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 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 */ 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 */ 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 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 343 protected void controlRender(RenderManager rm, ViewPort vp) { 344 } 345 346 @Override 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 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