/* 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$ * $Id$ * */ // Sprite management module #include "saga/saga.h" #include "saga/gfx.h" #include "saga/scene.h" #include "saga/resource.h" #include "saga/font.h" #include "saga/sprite.h" #include "saga/render.h" namespace Saga { #define RID_IHNM_ARROW_SPRITES 13 #define RID_IHNM_SAVEREMINDER_SPRITES 14 #define RID_IHNMDEMO_ARROW_SPRITES 8 #define RID_IHNMDEMO_SAVEREMINDER_SPRITES 9 Sprite::Sprite(SagaEngine *vm) : _vm(vm) { debug(8, "Initializing sprite subsystem..."); // Load sprite module resource context _spriteContext = _vm->_resource->getContext(GAME_RESOURCEFILE); if (_spriteContext == NULL) { error("Sprite::Sprite resource context not found"); } _decodeBufLen = DECODE_BUF_LEN; _decodeBuf = (byte *)malloc(_decodeBufLen); if (_decodeBuf == NULL) { memoryError("Sprite::Sprite"); } if (_vm->getGameId() == GID_ITE) { loadList(_vm->getResourceDescription()->mainSpritesResourceId, _mainSprites); _arrowSprites = _saveReminderSprites = _inventorySprites = _mainSprites; #ifdef ENABLE_IHNM } else if (_vm->getGameId() == GID_IHNM) { if (_vm->isIHNMDemo()) { loadList(RID_IHNMDEMO_ARROW_SPRITES, _arrowSprites); loadList(RID_IHNMDEMO_SAVEREMINDER_SPRITES, _saveReminderSprites); } else { loadList(RID_IHNM_ARROW_SPRITES, _arrowSprites); loadList(RID_IHNM_SAVEREMINDER_SPRITES, _saveReminderSprites); } #endif } else { error("Sprite: unknown game type"); } } Sprite::~Sprite() { debug(8, "Shutting down sprite subsystem..."); _mainSprites.freeMem(); if (_vm->getGameId() == GID_IHNM) { _inventorySprites.freeMem(); _arrowSprites.freeMem(); _saveReminderSprites.freeMem(); } free(_decodeBuf); } void Sprite::loadList(int resourceId, SpriteList &spriteList) { SpriteInfo *spriteInfo; byte *spriteListData = 0; size_t spriteListLength = 0; uint16 oldSpriteCount; uint16 newSpriteCount; uint16 spriteCount; int i; int outputLength, inputLength; uint32 offset; const byte *spritePointer; const byte *spriteDataPointer; _vm->_resource->loadResource(_spriteContext, resourceId, spriteListData, spriteListLength); if (spriteListLength == 0) { return; } MemoryReadStreamEndian readS(spriteListData, spriteListLength, _spriteContext->isBigEndian()); spriteCount = readS.readUint16(); debug(9, "Sprites: %d", spriteCount); oldSpriteCount = spriteList.spriteCount; newSpriteCount = spriteList.spriteCount + spriteCount; spriteList.infoList = (SpriteInfo *)realloc(spriteList.infoList, newSpriteCount * sizeof(*spriteList.infoList)); if (spriteList.infoList == NULL) { memoryError("Sprite::loadList"); } spriteList.spriteCount = newSpriteCount; bool bigHeader = _vm->getGameId() == GID_IHNM || _vm->isMacResources(); for (i = oldSpriteCount; i < spriteList.spriteCount; i++) { spriteInfo = &spriteList.infoList[i]; if (bigHeader) offset = readS.readUint32(); else offset = readS.readUint16(); if (offset >= spriteListLength) { // ITE Mac demos throw this warning warning("Sprite::loadList offset exceeded"); spriteList.spriteCount = i; return; } spritePointer = spriteListData; spritePointer += offset; if (bigHeader) { MemoryReadStreamEndian readS2(spritePointer, 8, _spriteContext->isBigEndian()); spriteInfo->xAlign = readS2.readSint16(); spriteInfo->yAlign = readS2.readSint16(); spriteInfo->width = readS2.readUint16(); spriteInfo->height = readS2.readUint16(); spriteDataPointer = spritePointer + readS2.pos(); } else { MemoryReadStreamEndian readS2(spritePointer, 4); spriteInfo->xAlign = readS2.readSByte(); spriteInfo->yAlign = readS2.readSByte(); spriteInfo->width = readS2.readByte(); spriteInfo->height = readS2.readByte(); spriteDataPointer = spritePointer + readS2.pos(); } outputLength = spriteInfo->width * spriteInfo->height; inputLength = spriteListLength - (spriteDataPointer - spriteListData); decodeRLEBuffer(spriteDataPointer, inputLength, outputLength); spriteInfo->decodedBuffer = (byte *) malloc(outputLength); if (spriteInfo->decodedBuffer == NULL) { memoryError("Sprite::loadList"); } #ifdef ENABLE_IHNM // IHNM sprites are upside-down, for reasons which i can only // assume are perverse. To simplify things, flip them now. Not // at drawing time. if (_vm->getGameId() == GID_IHNM) { byte *src = _decodeBuf + spriteInfo->width * (spriteInfo->height - 1); byte *dst = spriteInfo->decodedBuffer; for (int j = 0; j < spriteInfo->height; j++) { memcpy(dst, src, spriteInfo->width); src -= spriteInfo->width; dst += spriteInfo->width; } } else #endif memcpy(spriteInfo->decodedBuffer, _decodeBuf, outputLength); } free(spriteListData); } void Sprite::getScaledSpriteBuffer(SpriteList &spriteList, int spriteNumber, int scale, int &width, int &height, int &xAlign, int &yAlign, const byte *&buffer) { SpriteInfo *spriteInfo; if (spriteList.spriteCount <= spriteNumber) { // this can occur in IHNM while loading a saved game from chapter 1-5 when being in the end chapter warning("spriteList.spriteCount <= spriteNumber"); return; } spriteInfo = &spriteList.infoList[spriteNumber]; if (scale < 256) { xAlign = (spriteInfo->xAlign * scale) >> 8; yAlign = (spriteInfo->yAlign * scale) >> 8; height = (spriteInfo->height * scale + 0x7f) >> 8; width = (spriteInfo->width * scale + 0x7f) >> 8; scaleBuffer(spriteInfo->decodedBuffer, spriteInfo->width, spriteInfo->height, scale); buffer = _decodeBuf; } else { xAlign = spriteInfo->xAlign; yAlign = spriteInfo->yAlign; height = spriteInfo->height; width = spriteInfo->width; buffer = spriteInfo->decodedBuffer; } } void Sprite::drawClip(const Point &spritePointer, int width, int height, const byte *spriteBuffer, bool clipToScene) { int clipWidth; int clipHeight; Common::Rect clipRect = clipToScene ? _vm->_scene->getSceneClip() : _vm->getDisplayClip(); int i, j, jo, io; byte *bufRowPointer; const byte *srcRowPointer; bufRowPointer = _vm->_gfx->getBackBufferPixels() + _vm->_gfx->getBackBufferPitch() * spritePointer.y; srcRowPointer = spriteBuffer; clipWidth = CLIP(width, 0, clipRect.right - spritePointer.x); clipHeight = CLIP(height, 0, clipRect.bottom - spritePointer.y); jo = 0; io = 0; if (spritePointer.x < clipRect.left) { jo = clipRect.left - spritePointer.x; } if (spritePointer.y < clipRect.top) { io = clipRect.top - spritePointer.y; bufRowPointer += _vm->_gfx->getBackBufferPitch() * io; srcRowPointer += width * io; } for (i = io; i < clipHeight; i++) { for (j = jo; j < clipWidth; j++) { assert(_vm->_gfx->getBackBufferPixels() <= (byte *)(bufRowPointer + j + spritePointer.x)); assert((_vm->_gfx->getBackBufferPixels() + (_vm->getDisplayInfo().width * _vm->getDisplayInfo().height)) > (byte *)(bufRowPointer + j + spritePointer.x)); assert((const byte *)spriteBuffer <= (const byte *)(srcRowPointer + j)); assert(((const byte *)spriteBuffer + (width * height)) > (const byte *)(srcRowPointer + j)); if (*(srcRowPointer + j) != 0) { *(bufRowPointer + j + spritePointer.x) = *(srcRowPointer + j); } } bufRowPointer += _vm->_gfx->getBackBufferPitch(); srcRowPointer += width; } int x1 = MAX(spritePointer.x, 0); int y1 = MAX(spritePointer.y, 0); int x2 = MIN(MAX(spritePointer.x + clipWidth, 0), clipRect.right); int y2 = MIN(MAX(spritePointer.y + clipHeight, 0), clipRect.bottom); if (x2 > x1 && y2 > y1) _vm->_render->addDirtyRect(Common::Rect(x1, y1, x2, y2)); } void Sprite::draw(SpriteList &spriteList, int32 spriteNumber, const Point &screenCoord, int scale, bool clipToScene) { const byte *spriteBuffer = NULL; int width = 0; int height = 0; int xAlign = 0; int yAlign = 0; Point spritePointer; getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer); spritePointer.x = screenCoord.x + xAlign; spritePointer.y = screenCoord.y + yAlign; drawClip(spritePointer, width, height, spriteBuffer, clipToScene); } void Sprite::draw(SpriteList &spriteList, int32 spriteNumber, const Rect &screenRect, int scale, bool clipToScene) { const byte *spriteBuffer = NULL; int width = 0; int height = 0; int xAlign = 0; int spw; int yAlign = 0; int sph; Point spritePointer; getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer); spw = (screenRect.width() - width) / 2; sph = (screenRect.height() - height) / 2; if (spw < 0) { spw = 0; } if (sph < 0) { sph = 0; } spritePointer.x = screenRect.left + xAlign + spw; spritePointer.y = screenRect.top + yAlign + sph; drawClip(spritePointer, width, height, spriteBuffer, clipToScene); } bool Sprite::hitTest(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, const Point &testPoint) { const byte *spriteBuffer = NULL; int i, j; const byte *srcRowPointer; int width = 0; int height = 0; int xAlign = 0; int yAlign = 0; Point spritePointer; getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer); spritePointer.x = screenCoord.x + xAlign; spritePointer.y = screenCoord.y + yAlign; if ((testPoint.y < spritePointer.y) || (testPoint.y >= spritePointer.y + height)) { return false; } if ((testPoint.x < spritePointer.x) || (testPoint.x >= spritePointer.x + width)) { return false; } i = testPoint.y - spritePointer.y; j = testPoint.x - spritePointer.x; srcRowPointer = spriteBuffer + j + i * width; return *srcRowPointer != 0; } void Sprite::drawOccluded(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, int depth) { const byte *spriteBuffer = NULL; int x, y; byte *destRowPointer; const byte *sourceRowPointer; const byte *sourcePointer; byte *destPointer; byte *maskPointer; int width = 0; int height = 0; int xAlign = 0; int yAlign = 0; ClipData clipData; // BG mask variables int maskWidth; int maskHeight; byte *maskBuffer; size_t maskBufferLength; byte *maskRowPointer; int maskZ; if (!_vm->_scene->isBGMaskPresent()) { draw(spriteList, spriteNumber, screenCoord, scale); return; } _vm->_scene->getBGMaskInfo(maskWidth, maskHeight, maskBuffer, maskBufferLength); getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer); clipData.destPoint.x = screenCoord.x + xAlign; clipData.destPoint.y = screenCoord.y + yAlign; clipData.sourceRect.left = 0; clipData.sourceRect.top = 0; clipData.sourceRect.right = width; clipData.sourceRect.bottom = height; clipData.destRect = _vm->_scene->getSceneClip(); if (!clipData.calcClip()) { return; } // Finally, draw the occluded sprite sourceRowPointer = spriteBuffer + clipData.drawSource.x + (clipData.drawSource.y * width); destRowPointer = _vm->_gfx->getBackBufferPixels() + clipData.drawDest.x + (clipData.drawDest.y * _vm->_gfx->getBackBufferPitch()); maskRowPointer = maskBuffer + clipData.drawDest.x + (clipData.drawDest.y * maskWidth); for (y = 0; y < clipData.drawHeight; y++) { sourcePointer = sourceRowPointer; destPointer = destRowPointer; maskPointer = maskRowPointer; for (x = 0; x < clipData.drawWidth; x++) { if (*sourcePointer != 0) { maskZ = *maskPointer & SPRITE_ZMASK; if (maskZ > depth) { *destPointer = *sourcePointer; } } sourcePointer++; destPointer++; maskPointer++; } destRowPointer += _vm->_gfx->getBackBufferPitch(); maskRowPointer += maskWidth; sourceRowPointer += width; } _vm->_render->addDirtyRect(Common::Rect(clipData.drawSource.x, clipData.drawSource.y, clipData.drawSource.x + width, clipData.drawSource.y + height)); } void Sprite::decodeRLEBuffer(const byte *inputBuffer, size_t inLength, size_t outLength) { int bg_runcount; int fg_runcount; byte *outPointer; byte *outPointerEnd; int c; if (outLength > _decodeBufLen) { // TODO: may we should make dynamic growing? error("Sprite::decodeRLEBuffer outLength > _decodeBufLen"); } outPointer = _decodeBuf; outPointerEnd = _decodeBuf + outLength; outPointerEnd--; memset(outPointer, 0, outLength); MemoryReadStream readS(inputBuffer, inLength); while (!readS.eos() && (outPointer < outPointerEnd)) { bg_runcount = readS.readByte(); if (readS.eos()) break; fg_runcount = readS.readByte(); for (c = 0; c < bg_runcount && !readS.eos(); c++) { *outPointer = (byte) 0; if (outPointer < outPointerEnd) outPointer++; else return; } for (c = 0; c < fg_runcount && !readS.eos(); c++) { *outPointer = readS.readByte(); if (readS.eos()) break; if (outPointer < outPointerEnd) outPointer++; else return; } } } void Sprite::scaleBuffer(const byte *src, int width, int height, int scale) { byte skip = 256 - scale; // skip factor byte vskip = 0x80, hskip; byte *dst = _decodeBuf; for (int i = 0; i < height; i++) { vskip += skip; if (vskip < skip) { // We had an overflow src += width; } else { hskip = 0x80; for (int j = 0; j < width; j++) { *dst++ = *src++; hskip += skip; if (hskip < skip) // overflow dst--; } } } } } // End of namespace Saga