/******************************************************************************* Copyright (c) 2009, Charles McGarvey 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. 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 HOLDER 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. *******************************************************************************/ #include #include #include "Animation.hh" #include "Mippleton.hh" #include "Serializable.hh" namespace Mf { /** * The collection of nested animation classes. The animation implementation * consists of an Impl class which is allocated and initialized with the * interface object. This class contains the specific fields which are required * to run a single instance of an animation. The sequence data is loaded in a * different class which can be shared amongst multiple animation implementation * instances. */ class Animation::Impl { friend class Animation; /** * Contains "global" animation data for the various animations which get * loaded. This is a mippleton, so it will be shared amongst any animation * which wants to use these loaded sequences. */ class Data : public Mippleton { friend class Impl; friend class Mippleton; /** * A frame of an animation sequence. A frame is merely an index which * presumably represents a "slide" or tile which should be displayed, * and the duration that is how long the slide will be shown. */ struct Frame { unsigned index; ///< Frame index. Scalar duration; ///< Frame duration. /** * Construction is initialization. The frame data is loaded from a * frame map which is probably loaded within an animation file. */ Frame(SerializableP root) : index(0), duration(1.0) { Serializable::Map rootObj; if (root->get(rootObj)) { Serializable::Map::iterator it; for (it = rootObj.begin(); it != rootObj.end(); ++it) { std::string key = (*it).first; if (key == "index") { long value = 0; (*it).second->get(value); index = unsigned(value); } else if (key == "duration") { double value = 0.0; (*it).second->getNumber(value); duration = Scalar(value); } } } } }; /** * A sequence is just a few attributes and a list of frames in the order * that they should be played. */ struct Sequence { std::vector frames; ///< List of frames. Scalar delay; ///< Scale frame durations. bool loop; ///< Does the sequence repeat? std::string next; ///< Next sequence name. /** * Construction is initialization. The constructor loads sequence * data from the sequence map, presumably loaded from an animation * file. The rest of the loading takes place in the frame's * constructor which loads each individual frame. */ Sequence(SerializableP root) : delay(0.0), loop(true) { Serializable::Map rootObj; if (root->get(rootObj)) { Serializable::Map::iterator it; for (it = rootObj.begin(); it != rootObj.end(); ++it) { std::string key = (*it).first; if (key == "frames") { Serializable::Array framesObj; if ((*it).second->get(framesObj)) { Serializable::Array::iterator jt; for (jt = framesObj.begin(); jt != framesObj.end(); ++jt) { if (*jt) { frames.push_back(Frame(*jt)); } } } } else if (key == "delay") { double value; (*it).second->getNumber(value); delay = Scalar(value); } else if (key == "loop") { (*it).second->get(loop); } else if (key == "next") { (*it).second->get(next); } } } } }; /** * Starts loading a file with animation data. Such a file is formatted * as a map of named sequences. The sequence constructor loads each * individual sequence. */ void loadFromFile() { std::string filePath = Animation::getPath(getName()); Deserializer deserializer(filePath); SerializableP root = deserializer.deserialize(); if (root) { Serializable::Map rootObj; if (root->get(rootObj)) { Serializable::Map::iterator it; for (it = rootObj.begin(); it != rootObj.end(); ++it) { sequences.insert(std::pair((*it).first, Sequence((*it).second))); } } } } /** * Construction is initialization. The animation class data container * registers itself as a mippleton and then loads the animation data. */ explicit Data(const std::string& name) : Mippleton(name) { loadFromFile(); } std::map sequences; ///< List of sequences. }; /** * Construction is intialization. */ Impl(const std::string& name) : data(Data::getInstance(name)), currentSequence(0), frameCounter(0), frameIndex(0), timeAccum(0), frameDuration(0) {} /** * Sets up the animation classes to "play" a named sequence. If another * sequence was active, it will be replaced as the current sequence. Future * updates will progress the new sequence. */ void startSequence(const std::string& name) { std::map::iterator it; it = data->sequences.find(name); if (it != data->sequences.end()) { currentSequence = &(*it).second; frameCounter = 0; frameIndex = currentSequence->frames[0].index; timeAccum = 0.0; frameDuration = currentSequence->delay * currentSequence->frames[0].duration; } } /** * Updates or progresses the animation sequence. If the time interval * surpasses the duration of the current frame, a new frame becomes the * current frame. If the last frame of a sequence expires, the active * sequence will switch automatically to the designated "next" sequence, or * if none is specified but the sequence is set to loop, the first frame of * the sequence will become the current frame, and the animation essentially * starts over again. */ void update(Scalar t, Scalar dt) { if (currentSequence) { timeAccum += dt; if (timeAccum >= frameDuration) { if (++frameCounter >= currentSequence->frames.size()) { if (!currentSequence->next.empty()) { startSequence(currentSequence->next); } else if (currentSequence->loop) { frameCounter = 0; } else { frameCounter--; currentSequence = 0; } } frameIndex = currentSequence->frames[frameCounter].index; timeAccum = frameDuration - timeAccum; frameDuration = currentSequence->delay * currentSequence->frames[frameCounter].duration; } } } boost::shared_ptr data; ///< Internal data. Data::Sequence* currentSequence; ///< Active sequence. unsigned frameCounter; ///< Current frame. unsigned frameIndex; ///< Index of current frame. Scalar timeAccum; ///< Time accumulation. Scalar frameDuration; ///< Scaled frame duration. }; Animation::Animation(const std::string& name) : // pass through impl_(new Animation::Impl(name)) {} void Animation::startSequence(const std::string& name) { // pass through impl_->startSequence(name); } void Animation::update(Scalar t, Scalar dt) { // pass through impl_->update(t, dt); } /** * Gets the index for the current frame. This is presumably called by some * drawing code which will draw the correct current frame. */ unsigned Animation::getFrame() const { return impl_->frameIndex; } /** * Specialized search location for animation files. They can be found in the * "animations" subdirectory of any of the searched directories. */ std::string Animation::getPath(const std::string& name) { return Resource::getPath("animations/" + name + ".json"); } } // namespace Mf /** vim: set ts=4 sw=4 tw=80: *************************************************/