Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

anim.cpp

Go to the documentation of this file.
/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-0/engines/cine/anim.cpp $
 * $Id: anim.cpp 36127 2009-01-29 05:26:12Z fingolfin $
 *
 */

/*! \file
 * \todo Make resource manager class and make load* functions its members
 */

#include "common/endian.h"
#include "common/stream.h"

#include "cine/cine.h"
#include "cine/anim.h"
#include "cine/gfx.h"
#include "cine/pal.h"
#include "cine/part.h"
#include "cine/various.h"

00040 namespace Cine {

struct AnimHeader2Struct {
      uint32 field_0;
      uint16 width;
      uint16 height;
      uint16 type;
      uint16 field_A;
      uint16 field_C;
      uint16 field_E;
};

Common::Array<AnimData> animDataTable;

static const AnimDataEntry transparencyData[] = {
      {"ALPHA", 0xF},
      {"TITRE2", 0xF},
      {"ET", 0xC},
      {"L311", 0x3},
      {"L405", 0x1},
      {"L515", 0xC},
      {"L009", 0xE},
      {"L010", 0xE},
      {"FUTUR", 0x6},
      {"PAYSAN3", 0xB},
      {"L801", 0xC},
      {"L802", 0xC},
      {"L803", 0xC},
      {"L901", 0xD},
      {"L902", 0x8},
      {"L903", 0xD},
      {"L904", 0xD},
      {"L905", 0xD},
      {"L906", 0xD},
      {"L907", 0xD},
      {"LA03", 0x4},
      {"MOINE", 0xB},
      {"L908", 0x8},
      {"L909", 0x8},
      {"L807", 0xC},
      {"L808", 0xC},
      {"LA01", 0xB},
      {"L1201", 0xC},
      {"L1202", 0xC},
      {"L1203", 0xC},
      {"L1210", 0x5},
      {"L1211", 0xC},
      {"L1214", 0xC},
      {"L1215", 0xC},
      {"L1216", 0xC},
      {"L1217", 0xC},
      {"L1218", 0xC},
      {"L1219", 0xC},
      {"L1220", 0xC},
      {"SEIGNEUR", 0x6},
      {"PERE0", 0xD},
      {"L1302", 0x4},
      {"L1303", 0x4},
      {"L1304", 0x4},
      {"L1401", 0xF},
      {"L1402", 0xF},
      {"L1501", 0x8},
      {"L1503", 0x8},
      {"L1504", 0x4},
      {"L1505", 0x8},
      {"L1506", 0x8},
      {"L1601", 0xB},
      {"L1602", 0xB},
      {"L1603", 0xB},
      {"L1604", 0x4},
      {"L1605", 0x4},
      {"L1701", 0x4},
      {"L1702", 0x4},
      {"L1801", 0x6},
      {"L1904", 0x8},
      {"L2002", 0x8},
      {"L2003", 0x8},
      {"L2101", 0x4},
      {"L2102", 0x4},
      {"L2201", 0x7},
      {"L2202", 0x7},
      {"L2203", 0xE},
      {"L2305", 0x9},
      {"L2306", 0x9},
      {"GARDE1", 0x7},
      {"L2402", 0x7},
      {"L2407", 0x7},
      {"L2408", 0x7},
      {"GARDE2", 0x6},
      {"L2601", 0x6},
      {"L2602", 0x6},
      {"L2603", 0x6},
      {"L2604", 0x6},
      {"L2605", 0x8},
      {"L2606", 0x8},
      {"L2607", 0x8},
      {"L2610", 0x6},
      {"L2611", 0x6},
      {"L2612", 0x6},
      {"L2613", 0x8},
      {"L2614", 0x6},
      {"VOYAGEUR", 0x6},
      {"L2701", 0xD},
      {"L2702", 0xD},
      {"L2703", 0x6},
      {"L2801", 0xD},
      {"L2802", 0xD},
      {"L2803", 0xD},
      {"L2804", 0xD},
      {"L2807", 0xD},
      {"L2902", 0x8},
      {"L2903", 0x8},
      {"L3101", 0xA},
      {"L3102", 0xA},
      {"L3103", 0xA},
      {"L3203", 0xF},
      {"L3204", 0xF},
      {"L3001", 0x7},
      {"L3002", 0x7},
      {"L3416", 0xC},
      {"L3601", 0x5},
      {"L3602", 0x5},
      {"L3603", 0x5},
      {"L3607", 0x5},
      {"L3701", 0x8},
      {"L3702", 0x8},
      {"L3703", 0x8},
      {"L4001", 0xD},
      {"L4002", 0xD},
      {"L4103", 0xF},
      {"L4106", 0xF},
      {"CRUGHON1", 0xC},
      {"L4203", 0xC},
      {"L4301", 0xC},
      {"L4302", 0xC},
      {"L4303", 0xC},
      {"FUTUR2", 0x6},
      {"L4601", 0xE},
      {"L4603", 0x1},
      {"L4106", 0xF},
      {"L4801", 0xD},
      {"L4802", 0xD},
      {"FIN01", 0xB},
      {"FIN02", 0xB},
      {"FIN03", 0xB},
      {"FIN", 0x9},
};

void convertMask(byte *dest, const byte *source, int16 width, int16 height);
void convert8BBP(byte *dest, const byte *source, int16 width, int16 height);
void convert8BBP2(byte *dest, byte *source, int16 width, int16 height);

AnimData::AnimData() : _width(0), _height(0), _bpp(0), _var1(0), _data(NULL),
      _mask(NULL), _fileIdx(-1), _frameIdx(-1), _realWidth(0), _size(0) {

      memset(_name, 0, sizeof(_name));
}

/*! \brief Copy constructor
 */
AnimData::AnimData(const AnimData &src) : _width(src._width),
      _height(src._height), _bpp(src._bpp), _var1(src._var1),
      _data(NULL), _mask(NULL), _fileIdx(src._fileIdx),
      _frameIdx(src._frameIdx), _realWidth(src._realWidth), _size(src._size) {

      if (src._data) {
            _data = new byte[_size];
            assert(_data);
            memcpy(_data, src._data, _size*sizeof(byte));
      }

      if (src._mask) {
            _mask = new byte[_size];
            assert(_mask);
            memcpy(_mask, src._mask, _size*sizeof(byte));
      }

      memset(_name, 0, sizeof(_name));
      strcpy(_name, src._name);
}

/*! \brief Destructor
 */
AnimData::~AnimData() {
      clear();
}

/*! \brief Assingment operator
 */
AnimData &AnimData::operator=(const AnimData &src) {
      AnimData tmp = src;
      byte *ptr;

      _width = tmp._width;
      _height = tmp._height;
      _bpp = tmp._bpp;
      _var1 = tmp._var1;

      ptr = _data;
      _data = tmp._data;
      tmp._data = ptr;

      ptr = _mask;
      _mask = tmp._mask;
      tmp._mask = ptr;

      _fileIdx = tmp._fileIdx;
      _frameIdx = tmp._frameIdx;
      memset(_name, 0, sizeof(_name));
      strcpy(_name, tmp._name);
      _realWidth = tmp._realWidth;
      _size = tmp._size;

      return *this;
}

byte AnimData::getColor(int x, int y) {
      assert(_data);
      assert(x >= 0 && x < _realWidth && y >= 0 && y <= _height);
      assert(x + y * _realWidth < _size);

      return _data[x + y * _realWidth];
}

/*! \brief Load and decode image frame
 * \param d Encoded image data
 * \param type Encoding type
 * \param w Image width
 * \param h Image height
 * \param file Data file index in bundle
 * \param frame Image frame index
 * \param n Part name
 * \param transparent Transparent color (for ANIM_MASKSPRITE)
 */
void AnimData::load(byte *d, int type, uint16 w, uint16 h, int16 file,
      int16 frame, const char *n, byte transparent) {

      assert(d);

      if (_data) {
            clear();
      }

      _width = w * 2;
      _height = h;
      _var1 = _width >> 3;
      _data = NULL;
      _mask = NULL;
      _fileIdx = file;
      _frameIdx = frame;
      memset(_name, 0, sizeof(_name));
      strcpy(_name, n);
      _realWidth = w;

      switch (type) {
      case ANIM_RAW:
            _width = w;
            _var1 = w >> 3;
            _bpp = 4;
            _size = w * h;
            _data = new byte[_size];
            assert(_data);
            memcpy(_data, d, _size*sizeof(byte));
            break;

      case ANIM_MASK:
            _bpp = 1;
            _size = w * h * 8;
            _data = new byte[_size];
            _realWidth = w * 8;
            assert(_data);
            convertMask(_data, d, w, h);
            break;

      case ANIM_SPRITE:
            _bpp = 4;
            _size = w * h * 2;
            _data = new byte[_size];
            _realWidth = w * 2;
            assert(_data);
            gfxConvertSpriteToRaw(_data, d, w, h);
            break;

      case ANIM_MASKSPRITE:
            _bpp = 4;
            _size = w * h * 2;
            _data = new byte[_size];
            _mask = new byte[_size];
            _realWidth = w * 2;
            assert(_data && _mask);
            gfxConvertSpriteToRaw(_data, d, w, h);
            generateMask(_data, _mask, _size, transparent);
            break;

      case ANIM_PALSPRITE:
            _bpp = 5;
            _size = w * h * 2;
            _data = new byte[_size];
            _realWidth = w * 2;
            assert(_data);
            convert8BBP(_data, d, w, h);
            break;

      case ANIM_FULLSPRITE:
            _bpp = 8;
            _var1 = _width >> 4;
            _size = w * h;
            _data = new byte[_size];
            assert(_data);
            convert8BBP2(_data, d, w, h);
            break;

      default:
            error("AnimData::load: unknown image type");
      }
}

/*! \brief Reset image
 */
void AnimData::clear() {
      delete[] _data;
      delete[] _mask;

      _width = 0;
      _height = 0;
      _bpp = 0;
      _var1 = 0;
      _data = NULL;
      _mask = NULL;
      _fileIdx = -1;
      _frameIdx = -1;
      memset(_name, 0, sizeof(_name));
      _size = 0;
}

/*! \brief Write image identifiers to savefile
 * \param fHandle Savefile open for writing
 */
void AnimData::save(Common::OutSaveFile &fHandle) const {
      fHandle.writeUint16BE(_width);
      fHandle.writeUint16BE(_var1);
      fHandle.writeUint16BE(_bpp);
      fHandle.writeUint16BE(_height);
      fHandle.writeUint32BE(_data != NULL); // _data
      fHandle.writeUint32BE(_mask != NULL); // _mask
      fHandle.writeUint16BE(_fileIdx);
      fHandle.writeUint16BE(_frameIdx);
      fHandle.write(_name, sizeof(_name));
}

/*! \brief Clear part of animDataTable
 * \param startIdx First image frame to be cleared
 * \param numIdx Number of image frames to be cleared
 */
00394 void freeAnimDataRange(byte startIdx, byte numIdx) {
      for (byte i = 0; i < numIdx; i++) {
            animDataTable[startIdx + i].clear();
      }
}

/*! \brief Clear whole animDataTable
 */
00402 void freeAnimDataTable() {
      freeAnimDataRange(0, NUM_MAX_ANIMDATA);
}

/*! \brief Find transparent color index for image
 * \brief animName Image file name
 */
00409 static byte getAnimTransparentColor(const char *animName) {
      char name[15];

      removeExtention(name, animName);

      for (int i = 0; i < ARRAYSIZE(transparencyData); i++) {
            if (!strcmp(name, transparencyData[i].name)) {
                  return transparencyData[i].color;
            }
      }
      return 0;
}

/*! \brief Generate mask for image
 * \param[in] sprite Image data
 * \param[out] mask Image mask
 * \param size Image data length
 * \param transparency Transparent color index
 */
00428 void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency) {
      for (uint16 i = 0; i < size; i++) {
            if (*(sprite++) != transparency) {
                  *(mask++) = 0;
            } else {
                  *(mask++) = 1;
            }
      }
}

/*! \brief Decode 1bpp mask
 * \param[out] dest Decoded mask
 * \param[in] source Encoded mask
 * \param width Mask width
 * \param height Mask height
 */
00444 void convertMask(byte *dest, const byte *source, int16 width, int16 height) {
      int16 i, j;
      byte maskEntry;

      for (i = 0; i < width * height; i++) {
            maskEntry = *(source++);
            for (j = 0; j < 8; j++) {
                  *(dest++) = (maskEntry & 0x80) ? 0 : 1;
                  maskEntry <<= 1;
            }
      }
}

/*! \brief Decode 4bpp sprite
 * \param[out] dest Decoded image
 * \param[in] source Encoded image
 * \param width Image width
 * \param height Image height
 */
00463 void convert4BBP(byte *dest, const byte *source, int16 width, int16 height) {
      byte maskEntry;

      for (int16 i = 0; i < width * height; i++) {
            maskEntry = *(source++);
            *(dest++) = (maskEntry & 0xF0) >> 4;
            *(dest++) = (maskEntry & 0xF);
      }
}

/*! \brief Read image header
 * \param[out] animHeader Image header reference
 * \param readS Input stream open for reading
 */
00477 void loadAnimHeader(AnimHeaderStruct &animHeader, Common::MemoryReadStream readS) {
      animHeader.field_0 = readS.readByte();
      animHeader.field_1 = readS.readByte();
      animHeader.field_2 = readS.readByte();
      animHeader.field_3 = readS.readByte();
      animHeader.frameWidth = readS.readUint16BE();
      animHeader.frameHeight = readS.readUint16BE();
      animHeader.field_8 = readS.readByte();
      animHeader.field_9 = readS.readByte();
      animHeader.field_A = readS.readByte();
      animHeader.field_B = readS.readByte();
      animHeader.field_C = readS.readByte();
      animHeader.field_D = readS.readByte();
      animHeader.numFrames = readS.readUint16BE();
      animHeader.field_10 = readS.readByte();
      animHeader.field_11 = readS.readByte();
      animHeader.field_12 = readS.readByte();
      animHeader.field_13 = readS.readByte();
      animHeader.field_14 = readS.readUint16BE();
}

/*! \brief Find next empty space animDataTable
 * \param start First index to check
 */
00501 int emptyAnimSpace(int start = 0) {
      for (; start < NUM_MAX_ANIMDATA; start++) {
            if (!animDataTable[start].data()) {
                  return start;
            }
      }

      return -1;
}

/*! \brief Load SPL data into animDataTable
 * \param resourceName SPL filename
 * \param idx Target index in animDataTable (-1 if any empty space will do)
 * \return The number of the animDataTable entry after the loaded SPL data (-1 if error)
 */
00516 int loadSpl(const char *resourceName, int16 idx) {
      int16 foundFileIdx = findFileInBundle(resourceName);
      int entry;

      if (foundFileIdx < 0) {
            return -1;
      }

      byte *dataPtr = readBundleFile(foundFileIdx);

      entry = idx < 0 ? emptyAnimSpace() : idx;
      assert(entry >= 0);
      animDataTable[entry].load(dataPtr, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize, 1, foundFileIdx, 0, currentPartName);

      free(dataPtr);
      return entry + 1;
}

/*! \brief Load 1bpp mask
 * \param resourceName Mask filename
 * \param idx Target index in animDataTable (-1 if any empty space will do)
 * \return The number of the animDataTable entry after the loaded mask (-1 if error)
 */
00539 int loadMsk(const char *resourceName, int16 idx) {
      int16 foundFileIdx = findFileInBundle(resourceName);
      if (foundFileIdx < 0) {
            return -1;
      }

      int entry = 0;
      byte *dataPtr = readBundleFile(foundFileIdx);
      byte *ptr;
      AnimHeaderStruct animHeader;

      Common::MemoryReadStream readS(dataPtr, 0x16);
      loadAnimHeader(animHeader, readS);
      ptr = dataPtr + 0x16;

      entry = idx < 0 ? emptyAnimSpace() : idx;
      assert(entry >= 0);
      for (int16 i = 0; i < animHeader.numFrames; i++, entry++) {
            animDataTable[entry].load(ptr, ANIM_MASK, animHeader.frameWidth, animHeader.frameHeight, foundFileIdx, i, currentPartName);
            ptr += animHeader.frameWidth * animHeader.frameHeight;
      }

      free(dataPtr);
      return entry;
}

/*! \brief Load animation
 * \param resourceName Animation filename
 * \param idx Target index in animDataTable (-1 if any empty space will do)
 * \return The number of the animDataTable entry after the loaded animation (-1 if error)
 */
00570 int loadAni(const char *resourceName, int16 idx) {
      int16 foundFileIdx = findFileInBundle(resourceName);
      if (foundFileIdx < 0) {
            return -1;
      }

      int entry = 0;
      byte *dataPtr = readBundleFile(foundFileIdx);
      byte *ptr;
      byte transparentColor;
      AnimHeaderStruct animHeader;

      Common::MemoryReadStream readS(dataPtr, 0x16);
      loadAnimHeader(animHeader, readS);
      ptr = dataPtr + 0x16;

      transparentColor = getAnimTransparentColor(resourceName);

      // TODO: Merge this special case hack into getAnimTransparentColor somehow.
      // HACK: Versions of TITRE.ANI with height 37 use color 0xF for transparency.
      //       Versions of TITRE.ANI with height 57 use color 0x0 for transparency.
      //       Fixes bug #2057619: FW: Glitches in title display of demo (regression).
      if (scumm_stricmp(resourceName, "TITRE.ANI") == 0 && animHeader.frameHeight == 37) {
            transparentColor = 0xF;
      }

      entry = idx < 0 ? emptyAnimSpace() : idx;
      assert(entry >= 0);

      for (int16 i = 0; i < animHeader.numFrames; i++, entry++) {
            // special case transparency handling
            if (!strcmp(resourceName, "L2202.ANI")) {
                  transparentColor = i < 2 ? 0 : 7;
            } else if (!strcmp(resourceName, "L4601.ANI")) {
                  transparentColor = i < 1 ? 0xE : 0;
            }

            animDataTable[entry].load(ptr, ANIM_MASKSPRITE, animHeader.frameWidth, animHeader.frameHeight, foundFileIdx, i, currentPartName, transparentColor);
            ptr += animHeader.frameWidth * animHeader.frameHeight;
      }

      free(dataPtr);
      return entry;
}

/*! \brief Decode 16 color image with palette
 * \param[out] dest Decoded image
 * \param[in] source Encoded image
 * \param width Image width
 * \param height Image height
 */
00621 void convert8BBP(byte *dest, const byte *source, int16 width, int16 height) {
      const byte *table = source;
      byte color;

      source += 16;

      for (uint16 i = 0; i < width * height; i++) {
            color = *(source++);

            *(dest++) = table[color >> 4];
            *(dest++) = table[color & 0xF];
      }
}

/*! \brief Decode 8bit image
 * \param[out] dest Decoded image
 * \param[in] source Encoded image
 * \param width Image width
 * \param height Image height
 * \attention Data in source are destroyed during decoding
 */
00642 void convert8BBP2(byte *dest, byte *source, int16 width, int16 height) {
      uint16 i, j;
      int k, m;
      byte color;

      for (j = 0; j < (width * height) / 16; j++) {
            // m = 0: even bits, m = 1: odd bits
            for (m = 0; m <= 1; m++) {
                  for (i = 0; i < 8; i++) {
                        color = 0;
                        for (k = 14 + m; k >= 0; k -= 2) {
                              color |= ((*(source + k) & 0x080) >> 7);
                              *(source + k) <<= 1;
                              if (k > 0 + m)
                                    color <<= 1;
                        }     // end k
                        *(dest++) = color;
                  }     // end i
            }     // end m

            source += 0x10;
      }     // end j
}

/*! \brief Load image set
 * \param resourceName Image set filename
 * \param idx Target index in animDataTable (-1 if any empty space will do)
 * \return The number of the animDataTable entry after the loaded image set (-1 if error)
 */
00671 int loadSet(const char *resourceName, int16 idx) {
      AnimHeader2Struct header2;
      uint16 numSpriteInAnim;
      int16 foundFileIdx = findFileInBundle(resourceName);
      int16 entry;
      byte *ptr, *startOfDataPtr, *dataPtr, *origDataPtr;
      int type;

      if (foundFileIdx < 0) {
            return -1;
      }

      origDataPtr = dataPtr = readBundleFile(foundFileIdx);
      assert(!memcmp(dataPtr, "SET", 3));
      ptr = dataPtr + 4;

      numSpriteInAnim = READ_BE_UINT16(ptr);
      ptr += 2;

      startOfDataPtr = ptr + numSpriteInAnim * 0x10;

      entry = idx < 0 ? emptyAnimSpace() : idx;
      assert(entry >= 0);

      for (int16 i = 0; i < numSpriteInAnim; i++, entry++) {
            Common::MemoryReadStream readS(ptr, 0x10);

            header2.field_0 = readS.readUint32BE();
            header2.width = readS.readUint16BE();
            header2.height = readS.readUint16BE();
            header2.type = readS.readUint16BE();
            header2.field_A = readS.readUint16BE();
            header2.field_C = readS.readUint16BE();
            header2.field_E = readS.readUint16BE();

            ptr += 0x10;

            dataPtr = startOfDataPtr + header2.field_0;

            if (header2.type == 1) {
                  type = ANIM_MASK;
            } else if (header2.type == 4) {
                  type = ANIM_SPRITE;
            } else if (header2.type == 5) {
                  type = ANIM_PALSPRITE;
            } else {
                  type = ANIM_FULLSPRITE;
            }

            animDataTable[entry].load(dataPtr, type, header2.width, header2.height, foundFileIdx, i, currentPartName);
      }

      free(origDataPtr);
      return entry;
}

/*! \brief Load SEQ data into animDataTable
 * \param resourceName SEQ data filename
 * \param idx Target index in animDataTable (-1 if any empty space will do)
 * \return The number of the animDataTable entry after the loaded SEQ data (-1 if error)
 */
00732 int loadSeq(const char *resourceName, int16 idx) {
      int16 foundFileIdx = findFileInBundle(resourceName);
      if (foundFileIdx < 0) {
            return -1;
      }

      byte *dataPtr = readBundleFile(foundFileIdx);
      int entry = idx < 0 ? emptyAnimSpace() : idx;

      animDataTable[entry].load(dataPtr+0x16, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize-0x16, 1, foundFileIdx, 0, currentPartName);
      free(dataPtr);
      return entry + 1;
}

/*! \brief Load a resource into animDataTable
 * \param resourceName Resource's filename
 * \param idx Target index in animDataTable (-1 if any empty space will do)
 * \return The number of the animDataTable entry after the loaded resource (-1 if error)
 * \todo Implement loading of all resource types
 */
00752 int loadResource(const char *resourceName, int16 idx) {
      int result = -1; // Return an error by default
      if (strstr(resourceName, ".SPL")) {
            result = loadSpl(resourceName, idx);
      } else if (strstr(resourceName, ".MSK")) {
            result = loadMsk(resourceName, idx);
      } else if (strstr(resourceName, ".ANI")) {
            result = loadAni(resourceName, idx);
      } else if (strstr(resourceName, ".ANM")) {
            result = loadAni(resourceName, idx);
      } else if (strstr(resourceName, ".SET")) {
            result = loadSet(resourceName, idx);
      } else if (strstr(resourceName, ".SEQ")) {
            result = loadSeq(resourceName, idx);
      } else if (strstr(resourceName, ".H32")) {
            warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
      } else if (strstr(resourceName, ".AMI")) {
            warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
      } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure
            g_cine->quitGame();
      } else {
            error("loadResource: Cannot determine type for '%s'", resourceName);
      }

      return result;
}

} // End of namespace Cine

Generated by  Doxygen 1.6.0   Back to index