aboutsummaryrefslogtreecommitdiff
path: root/engines/saga/sprite.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/saga/sprite.cpp')
-rw-r--r--engines/saga/sprite.cpp444
1 files changed, 444 insertions, 0 deletions
diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp
new file mode 100644
index 0000000000..5309b1f109
--- /dev/null
+++ b/engines/saga/sprite.cpp
@@ -0,0 +1,444 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2004-2006 The ScummVM project
+ *
+ * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
+ *
+ * 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/resnames.h"
+#include "saga/rscfile.h"
+#include "saga/font.h"
+
+#include "saga/sprite.h"
+#include "saga/stream.h"
+
+namespace Saga {
+
+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->getGameType() == GType_ITE) {
+ loadList(_vm->getResourceDescription()->mainSpritesResourceId, _mainSprites);
+ _arrowSprites = _saveReminderSprites = _inventorySprites = _mainSprites;
+ } else {
+ loadList(RID_IHNM_ARROW_SPRITES, _arrowSprites);
+ loadList(RID_IHNM_SAVEREMINDER_SPRITES, _saveReminderSprites);
+ }
+}
+
+Sprite::~Sprite(void) {
+ debug(8, "Shutting down sprite subsystem...");
+ _mainSprites.freeMem();
+ free(_decodeBuf);
+}
+
+void Sprite::loadList(int resourceId, SpriteList &spriteList) {
+ SpriteInfo *spriteInfo;
+ byte *spriteListData;
+ size_t spriteListLength;
+ 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->getGameType() != GType_ITE || _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) {
+ error("Sprite::loadList offset exceed");
+ }
+
+ 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");
+ }
+
+ // 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->getGameType() == GType_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
+ 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;
+ assert(spriteList.spriteCount>spriteNumber);
+ 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(Surface *ds, const Rect &clipRect, const Point &spritePointer, int width, int height, const byte *spriteBuffer) {
+ int clipWidth;
+ int clipHeight;
+
+ int i, j, jo, io;
+ byte *bufRowPointer;
+ const byte *srcRowPointer;
+
+ bufRowPointer = (byte *)ds->pixels + ds->pitch * spritePointer.y;
+ srcRowPointer = spriteBuffer;
+
+ clipWidth = width;
+ if (width > (clipRect.right - spritePointer.x)) {
+ clipWidth = (clipRect.right - spritePointer.x);
+ }
+
+ clipHeight = height;
+ if (height > (clipRect.bottom - spritePointer.y)) {
+ clipHeight = (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 += ds->pitch * io;
+ srcRowPointer += width * io;
+ }
+
+ for (i = io; i < clipHeight; i++) {
+ for (j = jo; j < clipWidth; j++) {
+ assert((byte *)ds->pixels <= (byte *)(bufRowPointer + j + spritePointer.x));
+ assert(((byte *)ds->pixels + (_vm->getDisplayWidth() *
+ _vm->getDisplayHeight())) > (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 += ds->pitch;
+ srcRowPointer += width;
+ }
+}
+
+void Sprite::draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Point &screenCoord, int scale) {
+ const byte *spriteBuffer;
+ int width;
+ int height;
+ int xAlign;
+ int yAlign;
+ Point spritePointer;
+
+ getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
+
+ spritePointer.x = screenCoord.x + xAlign;
+ spritePointer.y = screenCoord.y + yAlign;
+
+ drawClip(ds, clipRect, spritePointer, width, height, spriteBuffer);
+}
+
+void Sprite::draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Rect &screenRect, int scale) {
+ const byte *spriteBuffer;
+ int width;
+ int height;
+ int xAlign, spw;
+ int yAlign, 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(ds, clipRect, spritePointer, width, height, spriteBuffer);
+}
+
+bool Sprite::hitTest(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, const Point &testPoint) {
+ const byte *spriteBuffer;
+ int i, j;
+ const byte *srcRowPointer;
+ int width;
+ int height;
+ int xAlign;
+ int yAlign;
+ 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(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, int depth) {
+ const byte *spriteBuffer;
+ int x, y;
+ byte *destRowPointer;
+ const byte *sourceRowPointer;
+ const byte *sourcePointer;
+ byte *destPointer;
+ byte *maskPointer;
+ int width;
+ int height;
+ int xAlign;
+ int yAlign;
+
+ ClipData clipData;
+
+ // BG mask variables
+ int maskWidth;
+ int maskHeight;
+ byte *maskBuffer;
+ size_t maskBufferLength;
+ byte *maskRowPointer;
+ int maskZ;
+
+ if (!_vm->_scene->isBGMaskPresent()) {
+ draw(ds, clipRect, 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 = clipRect;
+
+ if (!clipData.calcClip()) {
+ return;
+ }
+
+ // Finally, draw the occluded sprite
+
+ sourceRowPointer = spriteBuffer + clipData.drawSource.x + (clipData.drawSource.y * width);
+ destRowPointer = (byte *)ds->pixels + clipData.drawDest.x + (clipData.drawDest.y * ds->pitch);
+ 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 += ds->pitch;
+ maskRowPointer += maskWidth;
+ sourceRowPointer += width;
+ }
+}
+
+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();
+ 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 (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