/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* * This code is based on Broken Sword 2.5 engine * * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer * * Licensed under GNU GPL v2 * */ #include "sword25/gfx/animation.h" #include "sword25/kernel/kernel.h" #include "sword25/kernel/resmanager.h" #include "sword25/kernel/inputpersistenceblock.h" #include "sword25/kernel/outputpersistenceblock.h" #include "sword25/package/packagemanager.h" #include "sword25/gfx/image/image.h" #include "sword25/gfx/animationtemplate.h" #include "sword25/gfx/animationtemplateregistry.h" #include "sword25/gfx/animationresource.h" #include "sword25/gfx/bitmapresource.h" #include "sword25/gfx/graphicengine.h" namespace Sword25 { Animation::Animation(RenderObjectPtr parentPtr, const Common::String &fileName) : TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION) { // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. if (!_initSuccess) return; initMembers(); // Vom negativen Fall ausgehen. _initSuccess = false; initializeAnimationResource(fileName); // Erfolg signalisieren. _initSuccess = true; } Animation::Animation(RenderObjectPtr parentPtr, const AnimationTemplate &templ) : TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION) { // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. if (!_initSuccess) return; initMembers(); // Vom negativen Fall ausgehen. _initSuccess = false; _animationTemplateHandle = AnimationTemplate::create(templ); // Erfolg signalisieren. _initSuccess = true; } Animation::Animation(InputPersistenceBlock &reader, RenderObjectPtr parentPtr, uint handle) : TimedRenderObject(parentPtr, RenderObject::TYPE_ANIMATION, handle) { // Das BS_RenderObject konnte nicht erzeugt werden, daher muss an dieser Stelle abgebrochen werden. if (!_initSuccess) return; initMembers(); // Objekt vom Stream laden. _initSuccess = unpersist(reader); } void Animation::initializeAnimationResource(const Common::String &fileName) { // Die Resource wird für die gesamte Lebensdauer des Animations-Objektes gelockt. Resource *resourcePtr = Kernel::getInstance()->getResourceManager()->requestResource(fileName); if (resourcePtr && resourcePtr->getType() == Resource::TYPE_ANIMATION) _animationResourcePtr = static_cast(resourcePtr); else { error("The resource \"%s\" could not be requested. The Animation can't be created.", fileName.c_str()); return; } // Größe und Position der Animation anhand des aktuellen Frames bestimmen. computeCurrentCharacteristics(); } void Animation::initMembers() { _currentFrame = 0; _currentFrameTime = 0; _direction = FORWARD; _running = false; _finished = false; _relX = 0; _relY = 0; _scaleFactorX = 1.0f; _scaleFactorY = 1.0f; _modulationColor = 0xffffffff; _animationResourcePtr = 0; _animationTemplateHandle = 0; _framesLocked = false; _loopPointCallback = 0; _actionCallback = 0; _deleteCallback = 0; } Animation::~Animation() { if (getAnimationDescription()) { stop(); getAnimationDescription()->unlock(); } // Invoke the "delete" callback if (_deleteCallback) (_deleteCallback)(getHandle()); } void Animation::play() { // If the animation was completed, then play it again from the start. if (_finished) stop(); _running = true; lockAllFrames(); } void Animation::pause() { _running = false; unlockAllFrames(); } void Animation::stop() { _currentFrame = 0; _currentFrameTime = 0; _direction = FORWARD; pause(); } void Animation::setFrame(uint nr) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); if (nr >= animationDescriptionPtr->getFrameCount()) { error("Tried to set animation to illegal frame (%d). Value must be between 0 and %d.", nr, animationDescriptionPtr->getFrameCount()); return; } _currentFrame = nr; _currentFrameTime = 0; computeCurrentCharacteristics(); forceRefresh(); } bool Animation::doRender(RectangleList *updateRects) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); assert(_currentFrame < animationDescriptionPtr->getFrameCount()); // Bitmap des aktuellen Frames holen Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(_currentFrame).fileName); assert(pResource); assert(pResource->getType() == Resource::TYPE_BITMAP); BitmapResource *pBitmapResource = static_cast(pResource); // Framebufferobjekt holen GraphicEngine *pGfx = Kernel::getInstance()->getGfx(); assert(pGfx); // Bitmap zeichnen bool result; if (isScalingAllowed() && (_width != pBitmapResource->getWidth() || _height != pBitmapResource->getHeight())) { result = pBitmapResource->blit(_absoluteX, _absoluteY, (animationDescriptionPtr->getFrame(_currentFrame).flipV ? Graphics::FLIP_V : 0) | (animationDescriptionPtr->getFrame(_currentFrame).flipH ? Graphics::FLIP_H : 0), 0, _modulationColor, _width, _height, updateRects); } else { result = pBitmapResource->blit(_absoluteX, _absoluteY, (animationDescriptionPtr->getFrame(_currentFrame).flipV ? Graphics::FLIP_V : 0) | (animationDescriptionPtr->getFrame(_currentFrame).flipH ? Graphics::FLIP_H : 0), 0, _modulationColor, -1, -1, updateRects); } // Resource freigeben pBitmapResource->release(); return result; } void Animation::frameNotification(int timeElapsed) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); assert(timeElapsed >= 0); // Nur wenn die Animation läuft wird sie auch weiterbewegt if (_running) { // Gesamte vergangene Zeit bestimmen (inkl. Restzeit des aktuellen Frames) _currentFrameTime += timeElapsed; // Anzahl an zu überpringenden Frames bestimmen int skipFrames = animationDescriptionPtr->getMillisPerFrame() == 0 ? 0 : _currentFrameTime / animationDescriptionPtr->getMillisPerFrame(); // Neue Frame-Restzeit bestimmen _currentFrameTime -= animationDescriptionPtr->getMillisPerFrame() * skipFrames; // Neuen Frame bestimmen (je nach aktuellener Abspielrichtung wird addiert oder subtrahiert) int tmpCurFrame = _currentFrame; switch (_direction) { case FORWARD: tmpCurFrame += skipFrames; break; case BACKWARD: tmpCurFrame -= skipFrames; break; default: assert(0); } // Deal with overflows if (tmpCurFrame < 0) { // Loop-Point callback if (_loopPointCallback && !(_loopPointCallback)(getHandle())) _loopPointCallback = 0; // An underflow may only occur if the animation type is JOJO. assert(animationDescriptionPtr->getAnimationType() == AT_JOJO); tmpCurFrame = - tmpCurFrame; _direction = FORWARD; } else if (static_cast(tmpCurFrame) >= animationDescriptionPtr->getFrameCount()) { // Loop-Point callback if (_loopPointCallback && !(_loopPointCallback)(getHandle())) _loopPointCallback = 0; switch (animationDescriptionPtr->getAnimationType()) { case AT_ONESHOT: tmpCurFrame = animationDescriptionPtr->getFrameCount() - 1; _finished = true; pause(); break; case AT_LOOP: tmpCurFrame = tmpCurFrame % animationDescriptionPtr->getFrameCount(); break; case AT_JOJO: tmpCurFrame = animationDescriptionPtr->getFrameCount() - (tmpCurFrame % animationDescriptionPtr->getFrameCount()) - 1; _direction = BACKWARD; break; default: assert(0); } } if ((int)_currentFrame != tmpCurFrame) { forceRefresh(); if (animationDescriptionPtr->getFrame(_currentFrame).action != "") { // action callback if (_actionCallback && !(_actionCallback)(getHandle())) _actionCallback = 0; } } _currentFrame = static_cast(tmpCurFrame); } // Größe und Position der Animation anhand des aktuellen Frames bestimmen computeCurrentCharacteristics(); assert(_currentFrame < animationDescriptionPtr->getFrameCount()); assert(_currentFrameTime >= 0); } void Animation::computeCurrentCharacteristics() { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); assert(pResource); assert(pResource->getType() == Resource::TYPE_BITMAP); BitmapResource *pBitmap = static_cast(pResource); // Größe des Bitmaps auf die Animation übertragen _width = static_cast(pBitmap->getWidth() * _scaleFactorX); _height = static_cast(pBitmap->getHeight() * _scaleFactorY); // Position anhand des Hotspots berechnen und setzen int posX = _relX + computeXModifier(); int posY = _relY + computeYModifier(); RenderObject::setPos(posX, posY); pBitmap->release(); } bool Animation::lockAllFrames() { if (!_framesLocked) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); for (uint i = 0; i < animationDescriptionPtr->getFrameCount(); ++i) { if (!Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(i).fileName)) { error("Could not lock all animation frames."); return false; } } _framesLocked = true; } return true; } bool Animation::unlockAllFrames() { if (_framesLocked) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); for (uint i = 0; i < animationDescriptionPtr->getFrameCount(); ++i) { Resource *pResource; if (!(pResource = Kernel::getInstance()->getResourceManager()->requestResource(animationDescriptionPtr->getFrame(i).fileName))) { error("Could not unlock all animation frames."); return false; } // Zwei mal freigeben um den Request von LockAllFrames() und den jetzigen Request aufzuheben pResource->release(); if (pResource->getLockCount()) pResource->release(); } _framesLocked = false; } return true; } Animation::ANIMATION_TYPES Animation::getAnimationType() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); return animationDescriptionPtr->getAnimationType(); } int Animation::getFPS() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); return animationDescriptionPtr->getFPS(); } int Animation::getFrameCount() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); return animationDescriptionPtr->getFrameCount(); } bool Animation::isScalingAllowed() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); return animationDescriptionPtr->isScalingAllowed(); } bool Animation::isAlphaAllowed() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); return animationDescriptionPtr->isAlphaAllowed(); } bool Animation::isColorModulationAllowed() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); return animationDescriptionPtr->isColorModulationAllowed(); } void Animation::setPos(int relX, int relY) { _relX = relX; _relY = relY; computeCurrentCharacteristics(); } void Animation::setX(int relX) { _relX = relX; computeCurrentCharacteristics(); } void Animation::setY(int relY) { _relY = relY; computeCurrentCharacteristics(); } void Animation::setAlpha(int alpha) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); if (!animationDescriptionPtr->isAlphaAllowed()) { warning("Tried to set alpha value on an animation that does not support alpha. Call was ignored."); return; } uint newModulationColor = (_modulationColor & 0x00ffffff) | alpha << 24; if (newModulationColor != _modulationColor) { _modulationColor = newModulationColor; forceRefresh(); } } void Animation::setModulationColor(uint modulationColor) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); if (!animationDescriptionPtr->isColorModulationAllowed()) { warning("Tried to set modulation color on an animation that does not support color modulation. Call was ignored"); return; } uint newModulationColor = (modulationColor & 0x00ffffff) | (_modulationColor & 0xff000000); if (newModulationColor != _modulationColor) { _modulationColor = newModulationColor; forceRefresh(); } } void Animation::setScaleFactor(float scaleFactor) { setScaleFactorX(scaleFactor); setScaleFactorY(scaleFactor); } void Animation::setScaleFactorX(float scaleFactorX) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); if (!animationDescriptionPtr->isScalingAllowed()) { warning("Tried to set x scale factor on an animation that does not support scaling. Call was ignored"); return; } if (scaleFactorX != _scaleFactorX) { _scaleFactorX = scaleFactorX; if (_scaleFactorX <= 0.0f) _scaleFactorX = 0.001f; forceRefresh(); computeCurrentCharacteristics(); } } void Animation::setScaleFactorY(float scaleFactorY) { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); if (!animationDescriptionPtr->isScalingAllowed()) { warning("Tried to set y scale factor on an animation that does not support scaling. Call was ignored"); return; } if (scaleFactorY != _scaleFactorY) { _scaleFactorY = scaleFactorY; if (_scaleFactorY <= 0.0f) _scaleFactorY = 0.001f; forceRefresh(); computeCurrentCharacteristics(); } } const Common::String &Animation::getCurrentAction() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); return animationDescriptionPtr->getFrame(_currentFrame).action; } int Animation::getX() const { return _relX; } int Animation::getY() const { return _relY; } int Animation::getAbsoluteX() const { return _absoluteX + (_relX - _x); } int Animation::getAbsoluteY() const { return _absoluteY + (_relY - _y); } int Animation::computeXModifier() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); assert(pResource); assert(pResource->getType() == Resource::TYPE_BITMAP); BitmapResource *pBitmap = static_cast(pResource); int result = curFrame.flipV ? - static_cast((pBitmap->getWidth() - 1 - curFrame.hotspotX) * _scaleFactorX) : - static_cast(curFrame.hotspotX * _scaleFactorX); pBitmap->release(); return result; } int Animation::computeYModifier() const { AnimationDescription *animationDescriptionPtr = getAnimationDescription(); assert(animationDescriptionPtr); const AnimationResource::Frame &curFrame = animationDescriptionPtr->getFrame(_currentFrame); Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource(curFrame.fileName); assert(pResource); assert(pResource->getType() == Resource::TYPE_BITMAP); BitmapResource *pBitmap = static_cast(pResource); int result = curFrame.flipH ? - static_cast((pBitmap->getHeight() - 1 - curFrame.hotspotY) * _scaleFactorY) : - static_cast(curFrame.hotspotY * _scaleFactorY); pBitmap->release(); return result; } bool Animation::persist(OutputPersistenceBlock &writer) { bool result = true; result &= RenderObject::persist(writer); writer.write(_relX); writer.write(_relY); writer.write(_scaleFactorX); writer.write(_scaleFactorY); writer.write(_modulationColor); writer.write(_currentFrame); writer.write(_currentFrameTime); writer.write(_running); writer.write(_finished); writer.write(static_cast(_direction)); // Je nach Animationstyp entweder das Template oder die Ressource speichern. if (_animationResourcePtr) { uint32 marker = 0; writer.write(marker); writer.writeString(_animationResourcePtr->getFileName()); } else if (_animationTemplateHandle) { uint32 marker = 1; writer.write(marker); writer.write(_animationTemplateHandle); } else { assert(false); } //writer.write(_AnimationDescriptionPtr); writer.write(_framesLocked); // The following is only there to for compatibility with older saves // resp. the original engine. writer.write((uint32)1); writer.writeString("LuaLoopPointCB"); writer.write(getHandle()); writer.write((uint32)1); writer.writeString("LuaActionCB"); writer.write(getHandle()); writer.write((uint32)1); writer.writeString("LuaDeleteCB"); writer.write(getHandle()); result &= RenderObject::persistChildren(writer); return result; } // ----------------------------------------------------------------------------- bool Animation::unpersist(InputPersistenceBlock &reader) { bool result = true; result &= RenderObject::unpersist(reader); reader.read(_relX); reader.read(_relY); reader.read(_scaleFactorX); reader.read(_scaleFactorY); reader.read(_modulationColor); reader.read(_currentFrame); reader.read(_currentFrameTime); reader.read(_running); reader.read(_finished); uint32 direction; reader.read(direction); _direction = static_cast(direction); // Animationstyp einlesen. uint32 marker; reader.read(marker); if (marker == 0) { Common::String resourceFilename; reader.readString(resourceFilename); initializeAnimationResource(resourceFilename); } else if (marker == 1) { reader.read(_animationTemplateHandle); } else { assert(false); } reader.read(_framesLocked); if (_framesLocked) lockAllFrames(); // The following is only there to for compatibility with older saves // resp. the original engine. uint32 callbackCount; Common::String callbackFunctionName; uint32 callbackData; // loop point callback reader.read(callbackCount); assert(callbackCount == 1); reader.readString(callbackFunctionName); assert(callbackFunctionName == "LuaLoopPointCB"); reader.read(callbackData); assert(callbackData == getHandle()); // loop point callback reader.read(callbackCount); assert(callbackCount == 1); reader.readString(callbackFunctionName); assert(callbackFunctionName == "LuaActionCB"); reader.read(callbackData); assert(callbackData == getHandle()); // loop point callback reader.read(callbackCount); assert(callbackCount == 1); reader.readString(callbackFunctionName); assert(callbackFunctionName == "LuaDeleteCB"); reader.read(callbackData); assert(callbackData == getHandle()); // Set the callbacks setCallbacks(); result &= RenderObject::unpersistChildren(reader); return reader.isGood() && result; } // ----------------------------------------------------------------------------- AnimationDescription *Animation::getAnimationDescription() const { if (_animationResourcePtr) return _animationResourcePtr; else return AnimationTemplateRegistry::instance().resolveHandle(_animationTemplateHandle); } } // End of namespace Sword25