/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.animation; import com.jme3.export.*; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Mesh; import com.jme3.scene.Spatial; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.Control; import com.jme3.util.TempVars; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; /** * AnimControl is a Spatial control that allows manipulation * of skeletal animation. * * The control currently supports: * 1) Animation blending/transitions * 2) Multiple animation channels * 3) Multiple skins * 4) Animation event listeners * 5) Animated model cloning * 6) Animated model binary import/export * * Planned: * 1) Hardware skinning * 2) Morph/Pose animation * 3) Attachments * 4) Add/remove skins * * @author Kirill Vainer */ public final class AnimControl extends AbstractControl implements Cloneable { /** * Skeleton object must contain corresponding data for the targets' weight buffers. */ Skeleton skeleton; /** only used for backward compatibility */ @Deprecated private SkeletonControl skeletonControl; /** * List of animations */ HashMap animationMap; /** * Animation channels */ private transient ArrayList channels = new ArrayList(); /** * Animation event listeners */ private transient ArrayList listeners = new ArrayList(); /** * Creates a new animation control for the given skeleton. * The method {@link AnimControl#setAnimations(java.util.HashMap) } * must be called after initialization in order for this class to be useful. * * @param skeleton The skeleton to animate */ public AnimControl(Skeleton skeleton) { this.skeleton = skeleton; reset(); } /** * Serialization only. Do not use. */ public AnimControl() { } /** * Internal use only. */ public Control cloneForSpatial(Spatial spatial) { try { AnimControl clone = (AnimControl) super.clone(); clone.spatial = spatial; clone.channels = new ArrayList(); clone.listeners = new ArrayList(); if (skeleton != null) { clone.skeleton = new Skeleton(skeleton); } // animationMap is reference-copied, animation data should be shared // to reduce memory usage. return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(); } } /** * @param animations Set the animations that this AnimControl * will be capable of playing. The animations should be compatible * with the skeleton given in the constructor. */ public void setAnimations(HashMap animations) { animationMap = animations; } /** * Retrieve an animation from the list of animations. * @param name The name of the animation to retrieve. * @return The animation corresponding to the given name, or null, if no * such named animation exists. */ public Animation getAnim(String name) { if (animationMap == null) { animationMap = new HashMap(); } return animationMap.get(name); } /** * Adds an animation to be available for playing to this * AnimControl. * @param anim The animation to add. */ public void addAnim(Animation anim) { if (animationMap == null) { animationMap = new HashMap(); } animationMap.put(anim.getName(), anim); } /** * Remove an animation so that it is no longer available for playing. * @param anim The animation to remove. */ public void removeAnim(Animation anim) { if (!animationMap.containsKey(anim.getName())) { throw new IllegalArgumentException("Given animation does not exist " + "in this AnimControl"); } animationMap.remove(anim.getName()); } /** * Create a new animation channel, by default assigned to all bones * in the skeleton. * * @return A new animation channel for this AnimControl. */ public AnimChannel createChannel() { AnimChannel channel = new AnimChannel(this); channels.add(channel); return channel; } /** * Return the animation channel at the given index. * @param index The index, starting at 0, to retrieve the AnimChannel. * @return The animation channel at the given index, or throws an exception * if the index is out of bounds. * * @throws IndexOutOfBoundsException If no channel exists at the given index. */ public AnimChannel getChannel(int index) { return channels.get(index); } /** * @return The number of channels that are controlled by this * AnimControl. * * @see AnimControl#createChannel() */ public int getNumChannels() { return channels.size(); } /** * Clears all the channels that were created. * * @see AnimControl#createChannel() */ public void clearChannels() { channels.clear(); } /** * @return The skeleton of this AnimControl. */ public Skeleton getSkeleton() { return skeleton; } /** * Adds a new listener to receive animation related events. * @param listener The listener to add. */ public void addListener(AnimEventListener listener) { if (listeners.contains(listener)) { throw new IllegalArgumentException("The given listener is already " + "registed at this AnimControl"); } listeners.add(listener); } /** * Removes the given listener from listening to events. * @param listener * @see AnimControl#addListener(com.jme3.animation.AnimEventListener) */ public void removeListener(AnimEventListener listener) { if (!listeners.remove(listener)) { throw new IllegalArgumentException("The given listener is not " + "registed at this AnimControl"); } } /** * Clears all the listeners added to this AnimControl * * @see AnimControl#addListener(com.jme3.animation.AnimEventListener) */ public void clearListeners() { listeners.clear(); } void notifyAnimChange(AnimChannel channel, String name) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onAnimChange(this, channel, name); } } void notifyAnimCycleDone(AnimChannel channel, String name) { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onAnimCycleDone(this, channel, name); } } /** * Internal use only. */ @Override public void setSpatial(Spatial spatial) { if (spatial == null && skeletonControl != null) { this.spatial.removeControl(skeletonControl); } super.setSpatial(spatial); //Backward compatibility. if (spatial != null && skeletonControl != null) { spatial.addControl(skeletonControl); } } final void reset() { if (skeleton != null) { skeleton.resetAndUpdate(); } } /** * @return The names of all animations that this AnimControl * can play. */ public Collection getAnimationNames() { return animationMap.keySet(); } /** * Returns the length of the given named animation. * @param name The name of the animation * @return The length of time, in seconds, of the named animation. */ public float getAnimationLength(String name) { Animation a = animationMap.get(name); if (a == null) { throw new IllegalArgumentException("The animation " + name + " does not exist in this AnimControl"); } return a.getLength(); } /** * Internal use only. */ @Override protected void controlUpdate(float tpf) { if (skeleton != null) { skeleton.reset(); // reset skeleton to bind pose } TempVars vars = TempVars.get(); for (int i = 0; i < channels.size(); i++) { channels.get(i).update(tpf, vars); } vars.release(); if (skeleton != null) { skeleton.updateWorldVectors(); } } /** * Internal use only. */ @Override protected void controlRender(RenderManager rm, ViewPort vp) { } @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(skeleton, "skeleton", null); oc.writeStringSavableMap(animationMap, "animations", null); } @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule in = im.getCapsule(this); skeleton = (Skeleton) in.readSavable("skeleton", null); animationMap = (HashMap) in.readStringSavableMap("animations", null); if (im.getFormatVersion() == 0) { // Changed for backward compatibility with j3o files generated // before the AnimControl/SkeletonControl split. // If we find a target mesh array the AnimControl creates the // SkeletonControl for old files and add it to the spatial. // When backward compatibility won't be needed anymore this can deleted Savable[] sav = in.readSavableArray("targets", null); if (sav != null) { Mesh[] targets = new Mesh[sav.length]; System.arraycopy(sav, 0, targets, 0, sav.length); skeletonControl = new SkeletonControl(targets, skeleton); spatial.addControl(skeletonControl); } } } }