/* 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 3 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, see . * */ #include "bagel/spacebar/boflib/gfx/sprite.h" #include "bagel/boflib/misc.h" namespace Bagel { namespace SpaceBar { CBofRect *CBofSprite::_cDirtyRect; CBofSprite *CBofSprite::_pSpriteChain = nullptr; // Pointer to chain of linked sprites CBofSprite *CBofSprite::_pTouchedSprite = nullptr; // Pointer to sprite overlapped during painting CBofBitmap *CBofSprite::_pWorkBmp = nullptr; // Offscreen work area CBofPalette *CBofSprite::_pSharedPalette = nullptr; // Shared palette for ALL sprites int CBofSprite::_nWorkDX = 0; int CBofSprite::_nWorkDY = 0; void CBofSprite::initialize() { _cDirtyRect = new CBofRect(); _pSpriteChain = nullptr; _pTouchedSprite = nullptr; _pWorkBmp = nullptr; _pSharedPalette = nullptr; _nWorkDX = 0; _nWorkDY = 0; } void CBofSprite::shutdown() { delete _cDirtyRect; } void CBofSprite::openLibrary(CBofPalette *pPal) { // Must have a valid palette to do any sprite related stuff assert(pPal != nullptr); clearDirtyRect(); setSharedPalette(pPal); // Set up a default work area setupWorkArea(200, 200); } void CBofSprite::closeLibrary() { flushSpriteChain(); tearDownWorkArea(); _pSharedPalette = nullptr; } CBofSprite::CBofSprite() { _pImage = nullptr; // No initial bitmap image for the sprite _cSize = CBofSize(0, 0); // There is no size to the sprite image _cRect.setRectEmpty(); // Rectangular bounds not yet defined _cImageRect = _cRect; // Image rectangle starts same as display bounds _cPosition = CBofPoint(0, 0); // Default position to upper left corner of display _bPositioned = false; // Not yet positioned _bDuplicated = false; // Not sharing resources with other sprites _nZOrder = SPRITE_TOPMOST; // Default to top most in fore/back ground order _nCelCount = 1; // Number of frames in animated cel strip _nCelID = _nCelCount - 1; // Cel identifier not pointing at a cel _bAnimated = false; // Not initially animated _bLinked = false; // Not initially linked into the sprite chain _nMaskColor = NOT_TRANSPARENT; // Default to NO transparency _bReadOnly = true; setBlockAdvance(false); // Default always advance next sprite } CBofSprite::~CBofSprite() { assert(isValidObject(this)); unlinkSprite(); clearImage(); // Clear the sprite image bitmap and context } void CBofSprite::linkSprite() { assert(isValidObject(this)); if (!_bLinked) { // Set for linked into chain _bLinked = true; if (_pSpriteChain != nullptr) { switch (_nZOrder) { case SPRITE_TOPMOST: _pSpriteChain->addToTail(this); break; case SPRITE_HINDMOST: _pSpriteChain->addToHead(this); _pSpriteChain = this; break; default: { CBofSprite *pSprite; CBofSprite *pLastSprite = pSprite = _pSpriteChain; while (pSprite != nullptr && pSprite->_nZOrder > _nZOrder) { pLastSprite = pSprite; pSprite = (CBofSprite *)pSprite->_pNext; } pLastSprite->Insert(this); break; } } } else { _pSpriteChain = this; } // _pSpriteChain must always point to the head of the linked list assert(_pSpriteChain == (CBofSprite *)_pSpriteChain->getHead()); } } void CBofSprite::unlinkSprite() { assert(isValidObject(this)); if (_bLinked) { // Set for not linked into chain _bLinked = false; if (_pSpriteChain == this) _pSpriteChain = (CBofSprite *)_pNext; Delete(); } } void CBofSprite::flushSpriteChain() { CBofSprite *pSprite = getSpriteChain(); // Cycle getting head of chain, un-linking it and then deleting it while (pSprite != nullptr) { pSprite->unlinkSprite(); delete pSprite; pSprite = getSpriteChain(); } } void CBofSprite::setupWorkArea(int dx, int dy) { // Do we already have a work area? if (_pWorkBmp != nullptr) { // Yes, so lets tear it down before we start a new one tearDownWorkArea(); } // Create an offscreen bitmap where we do all the work; _pWorkBmp = new CBofBitmap(dx, dy, _pSharedPalette); _nWorkDX = dx; _nWorkDY = dy; } void CBofSprite::tearDownWorkArea() { delete _pWorkBmp; _pWorkBmp = nullptr; } CBofSprite *CBofSprite::duplicateSprite() { assert(isValidObject(this)); // Create an object for the sprite CBofSprite *pSprite = new CBofSprite; duplicateSprite(pSprite); return pSprite; } void CBofSprite::duplicateSprite(CBofSprite *pSprite) { if (!isValidObject(this) || (pSprite == nullptr)) error("duplicateSprite - Invalid source or destination sprite"); pSprite->_pImage = _pImage; pSprite->_cRect = _cRect; pSprite->_cImageRect = _cImageRect; pSprite->_cSize = _cSize; pSprite->_cPosition = _cPosition; pSprite->_nZOrder = _nZOrder; pSprite->_nCelID = _nCelID; pSprite->_nCelCount = _nCelCount; pSprite->_bAnimated = _bAnimated; pSprite->_nMaskColor = _nMaskColor; pSprite->_bDuplicated = true; // Mark it as a sprite with shared resources } bool CBofSprite::loadSprite(const char *pszPathName, int nCels) { assert(isValidObject(this)); assert(pszPathName != nullptr); assert(nCels >= 1); // Create an object for the sprite's image CBofBitmap *pBitmap = new CBofBitmap(pszPathName, _pSharedPalette); return loadSprite(pBitmap, nCels); } bool CBofSprite::loadSprite(CBofBitmap *pBitmap, int nCels) { assert(isValidObject(this)); // Can't load an invalid bitmap assert(pBitmap != nullptr); assert(nCels >= 1); clearImage(); // Clear out any/all existing bitmaps, palettes, _pImage = pBitmap; // Save pointer to bitmap pBitmap->setReadOnly(_bReadOnly); _cSize = pBitmap->getSize(); _cRect.setRect(0, 0, _cSize.cx - 1, _cSize.cy - 1); _cImageRect.setRect(0, 0, _cSize.cx - 1, _cSize.cy - 1); _nCelCount = 1; _nCelID = _nCelCount - 1; if (nCels != 1) { setupCels(nCels); // Assume it's animated _bAnimated = true; } return true; // Return success } bool CBofSprite::setupCels(const int nCels) { assert(isValidObject(this)); assert(nCels > 0); _nCelCount = nCels; // Set cel count _nCelID = _nCelCount - 1; // No current cel int nStripWidth = _cSize.cx; // Temp place toRetain cell strip pixel length _cSize.cx /= nCels; // Calculate width of a cel if (_cSize.cx * nCels == nStripWidth) { // Verify we have an even multiple _cRect.right = _cRect.left + _cSize.cx; // Reset sprite rectangular bounds _cRect.bottom = _cRect.top + _cSize.cy; // ... based on cel dimensions _cImageRect.setRect(0, 0, _cSize.cx - 1, _cSize.cy - 1); // Set bounds for first cel in strip return true; } return false; } void CBofSprite::nextCel() { assert(isValidObject(this)); // verify old cel id assert(_nCelID >= 0 && _nCelID < _nCelCount); if (getBlockAdvance() == false) { if (++_nCelID >= _nCelCount) _nCelID = 0; setCel(_nCelID); } } void CBofSprite::prevCel() { assert(isValidObject(this)); // verify old cel id assert(_nCelID >= 0 && _nCelID < _nCelCount); if (--_nCelID < 0) _nCelID = _nCelCount - 1; setCel(_nCelID); } bool CBofSprite::paintSprite(CBofWindow *pWnd, const int x, const int y) { assert(isValidObject(this)); // Can't paint to a non-existent window assert(pWnd != nullptr); // The window MUST have a backdrop assert(pWnd->getBackdrop() != nullptr); batchPaint(x, y); updateDirtyRect(pWnd, this); return !errorOccurred(); } bool CBofSprite::paintSprite(CBofBitmap *pBmp, const int x, const int y) { assert(isValidObject(this)); // Can't paint to a non-existent window assert(pBmp != nullptr); batchPaint(x, y); updateDirtyRect(pBmp, this); return !errorOccurred(); } bool CBofSprite::paintCel(CBofWindow *pWnd, int nCelId, const int x, const int y) { setCel(nCelId - 1); return paintSprite(pWnd, x, y); } bool CBofSprite::paintCel(CBofBitmap *pBmp, int nCelId, const int x, const int y) { setCel(nCelId - 1); return paintSprite(pBmp, x, y); } void CBofSprite::batchPaint(const int x, const int y) { assert(isValidObject(this)); CBofRect cDstRect; // Default to no sprite being overlapped by this painting operation _pTouchedSprite = nullptr; // Calculate destination rectangle cDstRect.setRect(x, y, x + _cSize.cx - 1, y + _cSize.cy - 1); // Add the destination position to the dirty rectangle list addToDirtyRect(&cDstRect); // If the sprite is already on screen, then we must also add it's old // current location to the dirty rect list so that it is erase properly if (_bPositioned) { addToDirtyRect(&_cRect); } // Now establish the sprite's new position setPosition(x, y); if (_bAnimated && (_nCelCount > 1)) // Advance to the next cel in the strip nextCel(); } bool CBofSprite::updateDirtyRect(CBofWindow *pWnd, CBofSprite *pPrimarySprite) { assert(pWnd != nullptr); // The window MUST have a backdrop associated with it. If that's not feasible, then // use CSprites instead of CBofSprites assert(pWnd->getBackdrop() != nullptr); // // Repaint the contents of the specified rectangle // CBofBitmap *pBackdrop = pWnd->getBackdrop(); if (pBackdrop != nullptr) { CBofRect *pRect = _cDirtyRect; if (pRect->width() != 0 && pRect->height() != 0) { // Need a work area CBofBitmap *pWork = _pWorkBmp; int dx = pRect->width(); int dy = pRect->height(); bool bTempWorkArea = false; if ((pWork == nullptr) || (dx > _nWorkDX) || (dy > _nWorkDY)) { bTempWorkArea = true; pWork = new CBofBitmap(dx, dy, _pSharedPalette); } pWork->lock(); // Paint the background into the work area pBackdrop->paint(pWork, 0, 0, pRect); // Only need to search the sprite list if current sprite is linked CBofSprite *pSprite = pPrimarySprite; if (pPrimarySprite == nullptr || pPrimarySprite->_bLinked) { pSprite = _pSpriteChain; } CBofRect cRect, cSrcRect; // Run through the sprite list while (pSprite != nullptr) { // and paint each partial sprite overlap to the work area if (pSprite->_bPositioned && cRect.intersectRect(&pSprite->_cRect, pRect)) { if (pPrimarySprite != pSprite) _pTouchedSprite = pSprite; cSrcRect = cRect - pSprite->_cRect.topLeft(); cSrcRect += pSprite->_cImageRect.topLeft(); cRect -= pRect->topLeft(); pSprite->_pImage->paint(pWork, &cRect, &cSrcRect, pSprite->_nMaskColor); } pSprite = (CBofSprite *)pSprite->_pNext; } // Paint final outcome to the screen cSrcRect.setRect(0, 0, pRect->width() - 1, pRect->height() - 1); pWork->paint(pWnd, pRect, &cSrcRect); pWork->unlock(); if (bTempWorkArea) { delete pWork; } } } clearDirtyRect(); return true; } bool CBofSprite::updateDirtyRect(CBofBitmap *pBmp, CBofSprite *pPrimarySprite) { assert(pBmp != nullptr); // // Repaint the contents of the specified rectangle // CBofRect *pRect = getDirtyRect(); // Only need to search the sprite list if current sprite is linked CBofSprite *pSprite = pPrimarySprite; if (pPrimarySprite == nullptr || pPrimarySprite->_bLinked) { pSprite = _pSpriteChain; } CBofRect cRect; // Run through the sprite list while (pSprite != nullptr) { // and paint each partial sprite overlap to the work area if (pSprite->_bPositioned && cRect.intersectRect(&pSprite->_cRect, pRect)) { if (pPrimarySprite != pSprite) _pTouchedSprite = pSprite; CBofRect cSrcRect = cRect - pSprite->_cRect.topLeft(); cSrcRect += pSprite->_cImageRect.topLeft(); pSprite->_pImage->paint(pBmp, &cRect, &cSrcRect, pSprite->_nMaskColor); } pSprite = (CBofSprite *)pSprite->_pNext; } clearDirtyRect(); return true; } void CBofSprite::addToDirtyRect(CBofRect *pRect) { assert(pRect != nullptr); CBofRect cRect; if (_cDirtyRect->isRectEmpty()) { cRect = *pRect; } else { cRect.unionRect(_cDirtyRect, pRect); } *_cDirtyRect = cRect; } void CBofSprite::setCel(const int nCelID) { assert(isValidObject(this)); // All sprites must have at least 1 frame assert(_nCelCount > 0); if (_nCelID != nCelID) { _nCelID = nCelID % _nCelCount; if ((_nCelID != 0) && (nCelID < 0)) { _nCelID = _nCelCount + _nCelID; } } // Verify new cel id assert(_nCelID >= 0 && _nCelID < _nCelCount); _cImageRect.left = _nCelID * _cSize.cx; _cImageRect.right = _cImageRect.left + _cSize.cx; } bool CBofSprite::eraseSprite(CBofWindow *pWnd) { assert(isValidObject(this)); assert(pWnd != nullptr); batchErase(); updateDirtyRect(pWnd); return !errorOccurred(); } void CBofSprite::batchErase() { if (_bPositioned) { _bPositioned = false; addToDirtyRect(&_cRect); } } bool CBofSprite::testInterception(CBofSprite *pTestSprite, CBofPoint *pPoint) { assert(isValidObject(this)); assert(pTestSprite != nullptr); // Punt if no interception allowed if (pTestSprite != nullptr) { // be sure to not test against ourself if (this != pTestSprite) { CBofRect overlapRect; // Area of overlap between rectangles // Use simple rectangle screening first if (overlapRect.intersectRect(&_cRect, &pTestSprite->_cRect)) { // ... and if that succeeds, see if we // ... have image masks that overlap if ((_nMaskColor == NOT_TRANSPARENT) || (pTestSprite->_nMaskColor == NOT_TRANSPARENT) || spritesOverlap(pTestSprite, pPoint)) { return true; } } } } return false; } CBofSprite *CBofSprite::interception(CBofRect *pNewRect, CBofSprite *pTestSprite) { assert(isValidObject(this)); assert(pNewRect != nullptr); // Get first sprite to be tested CBofSprite *pSprite = pTestSprite; // Thumb through the sprite chain while (pSprite != nullptr) { // be sure to not test against ourself // ... and only test against overlapping sprites if (this != pSprite) { CBofRect overlapRect; // Area of overlap between rectangles // Sprites touch if their rectangles intersect. // does our sprite overlap another? if (overlapRect.intersectRect(pNewRect, &pSprite->_cRect)) // ... if so return a pointer to it return pSprite; } // Fetch next sprite in chain for testing pSprite = (CBofSprite *)pSprite->_pNext; } return nullptr; } CBofSprite *CBofSprite::interception(CBofSprite *pTestSprite) { assert(isValidObject(this)); CBofSprite *pSprite = pTestSprite; // Get first sprite to be tested while (pSprite != nullptr) { // Thumb through the entire sprite collection if (testInterception(pSprite, nullptr)) // ... testing against each sprite in turn return pSprite; // found an interception pSprite = (CBofSprite *)pSprite->_pNext; // fetch next sprite in chain for testing } return nullptr; } bool CBofSprite::spritesOverlap(CBofSprite *pSprite, CBofPoint *pPoint) { assert(isValidObject(this)); assert(pSprite != nullptr); // Assume no overlap bool bHit = false; // If the sprite's rectangles overlap CBofRect overlapRect; if (overlapRect.intersectRect(&_cRect, &pSprite->_cRect)) { int32 dx = overlapRect.width(); int32 dy = overlapRect.height(); int32 x1 = overlapRect.left - _cRect.left + _cImageRect.left; int32 y1 = overlapRect.top - _cRect.top + _cImageRect.top; int32 x2 = overlapRect.left - pSprite->_cRect.left + pSprite->_cImageRect.left; int32 y2 = overlapRect.top - pSprite->_cRect.top + pSprite->_cImageRect.top; int32 dx1 = _pImage->widthBytes(); int32 dx2 = pSprite->_pImage->widthBytes(); byte m1 = (byte)_nMaskColor; byte m2 = (byte)pSprite->_nMaskColor; // Lock down these bitmaps _pImage->lock(); pSprite->_pImage->lock(); byte *pDib1 = (byte *)_pImage->getPixelAddress((int)x1, (int)y1); byte *pDib2 = (byte *)pSprite->_pImage->getPixelAddress((int)x2, (int)y2); if (!_pImage->isTopDown()) { dx1 = -dx1; } if (!pSprite->_pImage->isTopDown()) { dx2 = -dx2; } for (int32 y = 0; y < dy; y++) { byte *pPtr1 = pDib1; byte *pPtr2 = pDib2; for (int32 x = 0; x < dx; x++) { if ((*pPtr1 != m1) && (*pPtr2 != m2)) { if (pPoint != nullptr) { pPoint->x = (int)x; pPoint->y = (int)y; } bHit = true; goto endroutine; } pPtr1++; pPtr2++; } pDib1 += dx1; pDib2 += dx2; } } endroutine: // Don't need access to these bitmaps any more pSprite->_pImage->unlock(); _pImage->unlock(); return bHit; } void CBofSprite::setPosition(int x, int y) { assert(isValidObject(this)); // Now have a real location establish the new location of the sprite // and setup the bitmap's bounding rectangle _bPositioned = true; _cPosition.x = x; _cPosition.y = y; _cRect.setRect(_cPosition.x, _cPosition.y, _cPosition.x + _cSize.cx - 1, _cPosition.y + _cSize.cy - 1); } void CBofSprite::clearImage() { assert(isValidObject(this)); if (!_bDuplicated && (_pImage != nullptr)) { delete _pImage; } _pImage = nullptr; } void CBofSprite::setSharedPalette(CBofPalette *pPal) { assert(pPal != nullptr); _pSharedPalette = pPal; } void CBofSprite::setZOrder(int nValue) { assert(isValidObject(this)); assert(nValue >= SPRITE_TOPMOST && nValue <= SPRITE_HINDMOST); _nZOrder = nValue; // Relinking this sprite after setting it's new Z-Order will // add the sprite to the correct Z-Order sorted location (Insertion Sort) if (_bLinked) { unlinkSprite(); linkSprite(); } } } // namespace SpaceBar } // namespace Bagel