/******************************************************************************* 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 #include #include #include "Animation.hh" /** * 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 Mf::Library { friend class Impl; friend class Mf::Library; /** * 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. */ class Frame { friend class Impl; unsigned mIndex; ///< Frame index. Mf::Scalar mDuration; ///< Frame duration. /** * Construction is initialization. The frame data is loaded from a * frame map which is probably loaded within an animation file. */ Frame(Mf::Script& script, Mf::Script::Value table) : mIndex(0), mDuration(1.0) { table.pushField("index"); script[-1].get(mIndex); script.pop(); table.pushField("duration"); script[-1].get(mDuration); script.pop(); } }; /** * A sequence is just a few attributes and a list of frames in the order * that they should be played. */ struct Sequence { friend class Impl; std::vector mFrames; ///< List of frames. Mf::Scalar mDelay; ///< Scale frame durations. bool mLoop; ///< Does the sequence repeat? std::string mNext; ///< 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(Mf::Script& script, Mf::Script::Value table) : mDelay(0.0), mLoop(true) { table.pushField("delay"); script[-1].get(mDelay); script.pop(); table.pushField("loop"); script[-1].get(mLoop); script.pop(); table.pushField("next"); script[-1].get(mNext); script.pop(); // TODO - sequence class/type not yet implemented table.pushField("frames"); Mf::Script::Value frameTable = script.getTop(); if (frameTable.isTable()) { Mf::Script::Value top = script[-1]; int index = 1; for (;;) { script.push(index); frameTable.pushField(); if (top.isTable()) mFrames.push_back(Frame(script, top)); else break; ++index; } } script.pop(); } }; /** * 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() { Mf::Script script; std::string filePath = Animation::getPath(getName()); script.importStandardLibraries(); importLogScript(script); importAnimationBindings(script); if (script.doFile(filePath) != Mf::Script::SUCCESS) { std::string str; script[-1].get(str); Mf::logScript("%s", str.c_str()); } } int defineSequence(Mf::Script& script) { Mf::Script::Value name = script[1].requireString(); Mf::Script::Value table = script[2].requireTable(); std::string nameStr; name.get(nameStr); sequences.insert(std::pair(nameStr, Sequence(script, table))); return 0; } void importAnimationBindings(Mf::Script& script) { script.importFunction("DefineSequence", boost::bind(&Data::defineSequence, this, _1)); script.push(1); script.set("ATTACK"); script.push(2); script.set("CHARGE"); script.push(3); script.set("FLY"); script.push(4); script.set("HIT"); script.push(5); script.set("JUMP"); script.push(6); script.set("RUN"); script.push(7); script.set("STAND"); } /** * 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) : Mf::Library(name) { loadFromFile(); } std::map sequences; ///< List of sequences. }; /** * Construction is intialization. */ Impl(const std::string& name) : mData(Data::getInstance(name)), mCurrentSequence(0), mFrameCounter(0), mFrameIndex(0), mTimeAccum(0), mFrameDuration(0) {} /** * Sets up the animation classes to "play" a named sequence. If another * sequence was active, it will be replaced. Future updates will progress * the new sequence. */ void startSequence(const std::string& name) { std::map::iterator it; it = mData->sequences.find(name); if (it != mData->sequences.end()) { mCurrentSequence = &(*it).second; mFrameCounter = 0; mFrameIndex = mCurrentSequence->mFrames[0].mIndex; mTimeAccum = 0.0; mFrameDuration = mCurrentSequence->mDelay * mCurrentSequence->mFrames[0].mDuration; } } /** * 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(Mf::Scalar t, Mf::Scalar dt) { if (mCurrentSequence) { mTimeAccum += dt; if (mTimeAccum >= mFrameDuration) { if (++mFrameCounter >= mCurrentSequence->mFrames.size()) { if (!mCurrentSequence->mNext.empty()) { startSequence(mCurrentSequence->mNext); } else if (mCurrentSequence->mLoop) { mFrameCounter = 0; } else { mFrameCounter--; mCurrentSequence = 0; } } mFrameIndex = mCurrentSequence->mFrames[mFrameCounter].mIndex; mTimeAccum = mFrameDuration - mTimeAccum; mFrameDuration = mCurrentSequence->mDelay * mCurrentSequence->mFrames[mFrameCounter].mDuration; } } } boost::shared_ptr mData; ///< Internal data. Data::Sequence* mCurrentSequence; ///< Active sequence. unsigned mFrameCounter; ///< Current frame. unsigned mFrameIndex; ///< Index of current frame. Mf::Scalar mTimeAccum; ///< Time accumulation. Mf::Scalar mFrameDuration; ///< Scaled frame duration. }; Animation::Animation(const std::string& name) : // pass through mImpl(new Animation::Impl(name)) {} void Animation::startSequence(const std::string& name) { // pass through mImpl->startSequence(name); } void Animation::update(Mf::Scalar t, Mf::Scalar dt) { // pass through mImpl->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 mImpl->mFrameIndex; } /** * 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 Mf::Resource::getPath("animations/" + name + ".lua"); } /** vim: set ts=4 sw=4 tw=80: *************************************************/