/* 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. * */ #include "toltecs/toltecs.h" #include "toltecs/palette.h" #include "toltecs/render.h" #include "toltecs/resource.h" namespace Toltecs { class SpriteReader : public SpriteFilter { public: SpriteReader(byte *source, const SpriteDrawItem &sprite) : SpriteFilter(sprite), _source(source) { _curWidth = _sprite->origWidth; _curHeight = _sprite->origHeight; } SpriteReaderStatus readPacket(PixelPacket &packet) { if (_sprite->flags & 0x40) { // shadow sprite packet.count = _source[0] & 0x7F; if (_source[0] & 0x80) packet.pixel = 1; else packet.pixel = 0; _source++; } else if (_sprite->flags & 0x10) { // 256-color sprite packet.pixel = *_source++; packet.count = *_source++; } else { // 16-color sprite packet.count = _source[0] & 0x0F; packet.pixel = (_source[0] & 0xF0) >> 4; _source++; } _curWidth -= packet.count; if (_curWidth <= 0) { _curHeight--; if (_curHeight == 0) { return kSrsEndOfSprite; } else { _curWidth = _sprite->origWidth; return kSrsEndOfLine; } } else { return kSrsPixelsLeft; } } byte *getSource() { return _source; } void setSource(byte *source) { _source = source; _curHeight++; } protected: byte *_source; int16 _curWidth, _curHeight; }; class SpriteFilterScaleDown : public SpriteFilter { public: SpriteFilterScaleDown(const SpriteDrawItem &sprite, SpriteReader *reader) : SpriteFilter(sprite), _reader(reader) { _height = _sprite->height; _yerror = _sprite->yerror; _origHeight = _sprite->origHeight; _scalerStatus = 0; _xerror = 0; } SpriteReaderStatus readPacket(PixelPacket &packet) { SpriteReaderStatus status = kSrsPixelsLeft; if (_scalerStatus == 0) { _xerror = _sprite->xdelta; _yerror -= 100; while (_yerror <= 0) { do { status = _reader->readPacket(packet); } while (status == kSrsPixelsLeft); _yerror += _sprite->ydelta - 100; } if (status == kSrsEndOfSprite) return kSrsEndOfSprite; _scalerStatus = 1; } if (_scalerStatus == 1) { status = _reader->readPacket(packet); byte updcount = packet.count; while (updcount--) { _xerror -= 100; if (_xerror <= 0) { if (packet.count > 0) packet.count--; _xerror += _sprite->xdelta; } } if (status == kSrsEndOfLine) { if (--_height == 0) return kSrsEndOfSprite; _scalerStatus = 0; return kSrsEndOfLine; } } return kSrsPixelsLeft; } protected: SpriteReader *_reader; int16 _xerror, _yerror; int16 _height; int16 _origHeight; int _scalerStatus; }; class SpriteFilterScaleUp : public SpriteFilter { public: SpriteFilterScaleUp(const SpriteDrawItem &sprite, SpriteReader *reader) : SpriteFilter(sprite), _reader(reader) { _height = _sprite->height; _yerror = _sprite->yerror; _origHeight = _sprite->origHeight; _scalerStatus = 0; _sourcep = 0; _xerror = 0; } SpriteReaderStatus readPacket(PixelPacket &packet) { SpriteReaderStatus status; if (_scalerStatus == 0) { _xerror = _sprite->xdelta; _sourcep = _reader->getSource(); _scalerStatus = 1; } if (_scalerStatus == 1) { status = _reader->readPacket(packet); byte updcount = packet.count; while (updcount--) { _xerror -= 100; if (_xerror <= 0) { packet.count++; _xerror += _sprite->xdelta; } } if (status == kSrsEndOfLine) { if (--_height == 0) return kSrsEndOfSprite; _yerror -= 100; if (_yerror <= 0) { _reader->setSource(_sourcep); _yerror += _sprite->ydelta + 100; } _scalerStatus = 0; return kSrsEndOfLine; } } return kSrsPixelsLeft; } protected: SpriteReader *_reader; byte *_sourcep; int16 _xerror, _yerror; int16 _height; int16 _origHeight; int _scalerStatus; }; bool Screen::createSpriteDrawItem(const DrawRequest &drawRequest, SpriteDrawItem &sprite) { int16 scaleValueX, scaleValueY; int16 xoffs, yoffs; byte *spriteData; int16 frameNum; memset(&sprite, 0, sizeof(SpriteDrawItem)); if (drawRequest.flags == 0xFFFF) return false; frameNum = drawRequest.flags & 0x0FFF; sprite.flags = 0; sprite.baseColor = drawRequest.baseColor; sprite.x = drawRequest.x; sprite.y = drawRequest.y; sprite.priority = drawRequest.y; sprite.resIndex = drawRequest.resIndex; sprite.frameNum = frameNum; spriteData = _vm->_res->load(drawRequest.resIndex)->data; if (drawRequest.flags & 0x1000) { sprite.flags |= 4; } if (drawRequest.flags & 0x2000) { sprite.flags |= 0x10; } if (drawRequest.flags & 0x4000) { sprite.flags |= 0x40; } // First initialize the sprite item with the values from the sprite resource SpriteFrameEntry spriteFrameEntry(spriteData + frameNum * 12); if (spriteFrameEntry.w == 0 || spriteFrameEntry.h == 0) return false; sprite.offset = spriteFrameEntry.offset; sprite.width = spriteFrameEntry.w; sprite.height = spriteFrameEntry.h; sprite.origWidth = spriteFrameEntry.w; sprite.origHeight = spriteFrameEntry.h; if (drawRequest.flags & 0x1000) { xoffs = spriteFrameEntry.w - spriteFrameEntry.x; } else { xoffs = spriteFrameEntry.x; } yoffs = spriteFrameEntry.y; // If the sprite should be scaled we need to initialize some values now if (drawRequest.scaling != 0) { byte scaleValue = ABS(drawRequest.scaling); scaleValueX = scaleValue * sprite.origWidth; sprite.xdelta = (10000 * sprite.origWidth) / scaleValueX; scaleValueX /= 100; scaleValueY = scaleValue * sprite.origHeight; sprite.ydelta = (10000 * sprite.origHeight) / scaleValueY; scaleValueY /= 100; if (drawRequest.scaling > 0) { sprite.flags |= 2; sprite.width = sprite.origWidth + scaleValueX; sprite.height = sprite.origHeight + scaleValueY; xoffs += (xoffs * scaleValue) / 100; yoffs += (yoffs * scaleValue) / 100; } else { sprite.flags |= 1; sprite.width = sprite.origWidth - scaleValueX; sprite.height = sprite.origHeight - 1 - scaleValueY; if (sprite.width <= 0 || sprite.height <= 0) return false; xoffs -= (xoffs * scaleValue) / 100; yoffs -= (yoffs * scaleValue) / 100; } } sprite.x -= xoffs; sprite.y -= yoffs; sprite.yerror = sprite.ydelta; // Now we check if the sprite needs to be clipped // Clip Y if (sprite.y - _vm->_cameraY < 0) { int16 clipHeight = ABS(sprite.y - _vm->_cameraY); int16 skipHeight = clipHeight; byte *spriteFrameData; sprite.height -= clipHeight; if (sprite.height <= 0) return false; sprite.y = _vm->_cameraY; // If the sprite is scaled if (sprite.flags & 3) { int16 chopHeight = sprite.ydelta; if ((sprite.flags & 2) == 0) { do { chopHeight -= 100; if (chopHeight <= 0) { skipHeight++; chopHeight += sprite.ydelta; } else { clipHeight--; } } while (clipHeight > 0); } else { do { chopHeight -= 100; if (chopHeight < 0) { skipHeight--; chopHeight += sprite.ydelta + 100; } clipHeight--; } while (clipHeight > 0); } sprite.yerror = chopHeight; } spriteFrameData = spriteData + sprite.offset; // Now the sprite's offset is adjusted to point to the starting line if ((sprite.flags & 0x10) == 0) { while (skipHeight--) { int16 lineWidth = 0; while (lineWidth < sprite.origWidth) { sprite.offset++; lineWidth += spriteFrameData[0] & 0x0F; spriteFrameData++; } } } else { while (skipHeight--) { int16 lineWidth = 0; while (lineWidth < sprite.origWidth) { sprite.offset += 2; lineWidth += spriteFrameData[1]; spriteFrameData += 2; } } } } if (sprite.y + sprite.height - _vm->_cameraY - _vm->_cameraHeight > 0) sprite.height -= sprite.y + sprite.height - _vm->_cameraY - _vm->_cameraHeight; if (sprite.height <= 0) return false; sprite.skipX = 0; if (drawRequest.flags & 0x1000) { // Left border if (sprite.x - _vm->_cameraX < 0) { sprite.width -= ABS(sprite.x - _vm->_cameraX); sprite.x = _vm->_cameraX; } // Right border if (sprite.x + sprite.width - _vm->_cameraX - 640 > 0) { sprite.flags |= 8; sprite.skipX = sprite.x + sprite.width - _vm->_cameraX - 640; sprite.width -= sprite.skipX; } } else { // Left border if (sprite.x - _vm->_cameraX < 0) { sprite.flags |= 8; sprite.skipX = ABS(sprite.x - _vm->_cameraX); sprite.width -= sprite.skipX; sprite.x = _vm->_cameraX; } // Right border if (sprite.x + sprite.width - _vm->_cameraX - 640 > 0) { sprite.flags |= 8; sprite.width -= sprite.x + sprite.width - _vm->_cameraX - 640; } } if (sprite.width <= 0) return false; return true; } void Screen::addDrawRequest(const DrawRequest &drawRequest) { SpriteDrawItem sprite; if (createSpriteDrawItem(drawRequest, sprite)) _renderQueue->addSprite(sprite); } void Screen::drawSprite(const SpriteDrawItem &sprite) { debug(0, "Screen::drawSprite() x = %d; y = %d; flags = %04X; resIndex = %d; offset = %08X; drawX = %d; drawY = %d", sprite.x, sprite.y, sprite.flags, sprite.resIndex, sprite.offset, sprite.x - _vm->_cameraX, sprite.y - _vm->_cameraY); debug(0, "Screen::drawSprite() width = %d; height = %d; origWidth = %d; origHeight = %d", sprite.width, sprite.height, sprite.origWidth, sprite.origHeight); byte *source = _vm->_res->load(sprite.resIndex)->data + sprite.offset; byte *dest = _frontScreen + sprite.x + sprite.y * 640; SpriteReader spriteReader(source, sprite); if (sprite.flags & 0x40) { // Shadow sprites if (sprite.flags & 1) { SpriteFilterScaleDown spriteScaler(sprite, &spriteReader); drawSpriteCore(dest, spriteScaler, sprite); } else if (sprite.flags & 2) { SpriteFilterScaleUp spriteScaler(sprite, &spriteReader); drawSpriteCore(dest, spriteScaler, sprite); } else { drawSpriteCore(dest, spriteReader, sprite); } } else if (sprite.flags & 0x10) { // 256 color sprite drawSpriteCore(dest, spriteReader, sprite); } else { // 16 color sprite if (sprite.flags & 1) { SpriteFilterScaleDown spriteScaler(sprite, &spriteReader); drawSpriteCore(dest, spriteScaler, sprite); } else if (sprite.flags & 2) { SpriteFilterScaleUp spriteScaler(sprite, &spriteReader); drawSpriteCore(dest, spriteScaler, sprite); } else { drawSpriteCore(dest, spriteReader, sprite); } } debug(0, "Screen::drawSprite() ok"); } void Screen::drawSpriteCore(byte *dest, SpriteFilter &reader, const SpriteDrawItem &sprite) { int16 destInc; if (sprite.flags & 4) { destInc = -1; dest += sprite.width; } else { destInc = 1; } SpriteReaderStatus status; PixelPacket packet; byte *destp = dest; int16 skipX = sprite.skipX; int16 w = sprite.width; int16 h = sprite.height; do { status = reader.readPacket(packet); if (skipX > 0) { while (skipX > 0) { skipX -= packet.count; if (skipX < 0) { packet.count = ABS(skipX); break; } status = reader.readPacket(packet); } } if (w - packet.count < 0) packet.count = w; w -= packet.count; if (((sprite.flags & 0x40) && (packet.pixel != 0)) || ((sprite.flags & 0x10) && (packet.pixel != 0xFF)) || (!(sprite.flags & 0x10) && (packet.pixel != 0))) { if (sprite.flags & 0x40) { while (packet.count--) { *dest = _vm->_palette->getColorTransPixel(*dest); dest += destInc; } } else { if (sprite.flags & 0x10) { packet.pixel = ((packet.pixel << 4) & 0xF0) | ((packet.pixel >> 4) & 0x0F); } else { packet.pixel += sprite.baseColor - 1; } while (packet.count--) { *dest = packet.pixel; dest += destInc; } } } else { dest += packet.count * destInc; } if (status == kSrsEndOfLine || w <= 0) { if (w <= 0) { while (status == kSrsPixelsLeft) { status = reader.readPacket(packet); } } dest = destp + 640; destp = dest; skipX = sprite.skipX; w = sprite.width; h--; } } while (status != kSrsEndOfSprite && h > 0); } } // End of namespace Toltecs