/* 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/hodjnpodj/hnplibs/stdafx.h" #include "bagel/hodjnpodj/hnplibs/sprite.h" namespace Bagel { namespace HodjNPodj { #define SPRITE_DEBUG 0 // set for debugging sprite reconstruction int CSprite::m_nIndex = 0; // used for generating unique Ids CSprite *CSprite::m_pSpriteChain = nullptr; // pointer to chain of linked sprites bool CSprite::m_bTouchedSprite = false; // set if sprite overlapped during painting CSprite *CSprite::m_pTouchedSprite = nullptr; // pointer to sprite overlapped during painting bool CSprite::m_bHaveBackdrop = false; // whether we have a bitmap for the background CDC *CSprite::m_pBackdropDC = nullptr; // backdrop context for background painting CBitmap *CSprite::m_pBackdrop = nullptr; // backdrop for background painting CBitmap *CSprite::m_pBackdropOld = nullptr; // previous bitmap mapped to backdrop context CPalette *CSprite::m_pBackdropPalette = nullptr; // palette mapped to backdrop context CPalette *CSprite::m_pBackdropPalOld = nullptr; // previous palette mapped to backdrop context IMPLEMENT_DYNCREATE(CSprite, CObject) /************************************************************************* * * CSprite() * * Parameters: none * * Return Value: none * * Description: Constructor for sprite class. Initialize all fields * to logical nullptr. Calls should then be made to other * sprite routines to load palettes and bitmaps. * ************************************************************************/ CSprite::CSprite() { m_pImageDC = nullptr; // no device context allocated for sprite image m_pImage = nullptr; // no initial bitmap image for the sprite m_pImageOld = nullptr; // ... hence no unmapped bitmap for the context m_pMaskDC = nullptr; // no device context allocated for sprite mask m_pMask = nullptr; // no initial image mask m_pMaskOld = nullptr; // ... hence no unmapped bitmap for the context m_pBackgroundDC = nullptr; // no device context allocated for background m_pBackground = nullptr; // no initial background mask m_pBackgroundOld = nullptr; // ... hence no unmapped bitmap for the context m_pPalette = nullptr; // no initial palette assigned to this sprite m_pPalImageOld = nullptr; // ... hence no unmapped palette for the image m_pPalBackOld = nullptr; // ... and no unmapped palette for the background m_bSharedPalette = false; // ... and thus it is not currently shared m_cSize = CSize(0, 0); // there is no size to the sprite image m_cRect = CRect(0, 0, 0, 0); // rectangular bounds not yet defined m_cImageRect = m_cRect; // image rectangle starts same as display bounds m_cPosition = CPoint(0, 0); // default position to upper left corner of display m_cMovementDelta = CPoint(0, 0); // no initial movement rate m_bPositioned = false; // not yet positioned m_cHotspot = CPoint(0, 0); // default the hot spot to upper left corner m_bVisible = false; // sprite has no initial image m_bMasked = false; // simple unmasked bitmap m_bMobile = false; // it is a stationary sprite m_bOverlaps = false; // does not yet overlap other sprites m_bIntercepts = true; // can be detected by interception m_bRetainBackground = true; // retain background image for updates m_bRetainContexts = false; // do not retain device contexts m_bDuplicated = false; // not sharing resources with other sprites m_nId = ++m_nIndex; // set its unique identifirer m_nType = 0; // no user defined information m_pData = nullptr; // no user define pointer information m_nZOrder = SPRITE_TOPMOST; // default to top most in fore/back ground order m_nZPosition = SPRITE_TOPMOST; // default to top most in fore/back ground order m_nCelID = -1; // cel identifier not pointing at a cel m_nCelCount = 0; // no cels to be played m_bAnimated = false; // not initially animated m_bLinked = false; // not initially linked into the sprite chain m_pNext = nullptr; // it is not yet in the sprite chain and m_pPrev = nullptr; // ... thus has no links to other sprites m_pZPrev = nullptr; } /************************************************************************* * * ~CSprite() * * Parameters: none * * Return Value: none * * Description: Destructor for sprite class. It is important to tear * things down in the order we built them; map out bitmap, * map out the palette, then discard the device context - * failure to release key resources will cause application * or system crashes. * ************************************************************************/ CSprite::~CSprite() { ClearImage(); // clear the sprite image bitmap and context ClearMask(); // clear the sprite mask bitmap ClearBackground(); // clear the sprite background bitmap and context ClearPalette(); // clear the palette resource if (m_bLinked) // unlink from chain if still hooked in UnlinkSprite(); } /************************************************************************* * * LinkSprite() * * Parameters: none * * Return Value: none * * Description: Link this sprite into the chain by placing it at the * the head of the chain * ************************************************************************/ void CSprite::LinkSprite() { m_bLinked = true; // set for linked into chain if ((m_pPrev != nullptr) || // punt if already in chain (m_pNext != nullptr)) return; m_pNext = m_pSpriteChain; // link sprite into head of chain m_pPrev = nullptr; // ... by pointing it at the current if (m_pSpriteChain != nullptr) // ... initial sprite, and pointing it (*m_pSpriteChain).m_pPrev = this; // ... back at us m_pSpriteChain = this; } /************************************************************************* * * UnLinkSprite() * * Parameters: none * * Return Value: none * * Description: Remove this sprite from the sprite chain and point its * neighbors at each other to fill the gap * ************************************************************************/ void CSprite::UnlinkSprite() { m_bLinked = false; // set for not linked into chain if ((m_pPrev == nullptr) && // punt if not in chain (m_pNext == nullptr) && (m_pSpriteChain != this)) return; if (m_pPrev != nullptr) // disconnect us from the sprite chain (*m_pPrev).m_pNext = m_pNext; // ... by pointing the one before us, and else // ... the one after us, at each other m_pSpriteChain = m_pNext; // special case the instance where the // ... sprite to be removed is the first if (m_pNext != nullptr) // ... in the list - in particular, update (*m_pNext).m_pPrev = m_pPrev; // ... the head of chain pointer m_pNext = m_pPrev = nullptr; } /************************************************************************* * * FlushSpriteChain() * * Parameters: none * * Return Value: none * * Description: Remove all sprites from the chain and delete them via * the standard destructor * ************************************************************************/ void CSprite::FlushSpriteChain() { CSprite *pSprite = nullptr; while ((pSprite = CSprite::GetSpriteChain()) != nullptr) { // cycle getting head of chain (*pSprite).UnlinkSprite(); // ... unlinking it delete pSprite; } // ... and then deleting it } /************************************************************************* * * SetBackdrop() * * Parameters: * CDC *pDC screen device context * CPalette *pPalette palette to use for painting * CBitmap *pBitmap pointer to screen-size bitmap for background painting * * Return Value: * bool success/failure condition * * Description: establish the bitmap used for restoring the background * as sprite images are moved around the screen. * ************************************************************************/ bool CSprite::SetBackdrop(CDC *pDC, CPalette *pPalette, CBitmap *pBitmap) { if (m_pBackdropDC != nullptr) // if the backdrop context is active ReleaseBackdropDC(); // ... then unmap all of its resources if (m_pBackdrop != nullptr) // delete the old bitmap delete m_pBackdrop; m_pBackdrop = pBitmap; // save the new bitmap m_pBackdropPalette = pPalette; // ... and palette m_bHaveBackdrop = true; // indicate we have a backdrop return true; } /************************************************************************* * * ClearBackdrop() * * Parameters: none * * Return Value: none * * Description: clear the bitmap and context used for restoring the * background as sprite images were moved around the screen. * ************************************************************************/ void CSprite::ClearBackdrop() { ReleaseBackdropDC(); // unmap any resources if (m_pBackdrop != nullptr) { // delete the backdrop bitmap delete m_pBackdrop; m_pBackdrop = nullptr; } m_pBackdropPalette = nullptr; if (m_pBackdropDC != nullptr) { // delete the device context delete m_pBackdropDC; m_pBackdropDC = nullptr; } m_bHaveBackdrop = false; } /************************************************************************* * * RefreshBackdrop() * * Parameters: * CDC *pDC screen device context * CPalette *pPalette palette to use for painting * * Return Value: * bool success/failure condition * * Description: repaint the backdrop. * ************************************************************************/ bool CSprite::RefreshBackdrop(CDC *pDC, CPalette *pPalette) { bool bSuccess = false; CPalette *pPalOld = nullptr; if (m_bHaveBackdrop == false) // punt if no backdrop art return false; if (GetBackdropDC(pDC) == nullptr) // setup backdrop context return false; if (pPalette != nullptr) { // map in our palette pPalOld = (*pDC).SelectPalette(pPalette, false); (*pDC).RealizePalette(); } // zap it to the screen bSuccess = (*pDC).BitBlt(0, 0, GAME_WIDTH, GAME_HEIGHT, m_pBackdropDC, 0, 0, SRCCOPY); if (pPalOld != nullptr) // map out the palette (*pDC).SelectPalette(pPalOld, false); ReleaseBackdropDC(); // release backdrop context resources return bSuccess; } /************************************************************************* * * DuplicateSprite() * * Parameters: * CDC *pDC pointer to device context to be used in creating * the sprite * * Return Value: * CSprite * pointer to new sprite (Success) / nullptr (failure) * * Description: Create a sprite based on sharing the resources of a * master sprite (passed implicitly when this is invoked). * * NOTE: Do NOT delete a sprite that has been duplicated * until ALL of its duplicates are deleted; using a duplicate * when its master has been deleted will result in a crash. * ************************************************************************/ CSprite *CSprite::DuplicateSprite(CDC *pDC) { CSprite *pSprite = nullptr; pSprite = new CSprite(); // create an object for the sprite if (DuplicateSprite(pDC, pSprite)) return pSprite; delete pSprite; return nullptr; // return failure } /************************************************************************* * * DuplicateSprite() * * Parameters: * CDC *pDC pointer to device context to be used in creating * the sprite * CSprite *pSprite pointer to sprite to be duplicated * * Return Value: * bool success/failure condition * * Description: Update a sprite based on sharing the resources of a * master sprite (passed implicitly when this is invoked). * * NOTE: Do NOT delete a sprite that has been duplicated * until ALL of its duplicates are deleted; using a duplicate * when its master has been deleted will result in a crash. * ************************************************************************/ bool CSprite::DuplicateSprite(CDC *pDC, CSprite *pSprite) { CPalette *pPalOld = nullptr; if (m_pPalette) { pPalOld = (*pDC).SelectPalette(m_pPalette, false); (*pDC).RealizePalette(); } if (pSprite != nullptr) { // only duplicate visible sprites // ... unable to create that object if (m_bVisible) { if (m_bRetainContexts) { if (!SetupImage(pDC)) return false; (void)SetupMask(pDC); (*pSprite).m_pImageDC = m_pImageDC; (*pSprite).m_pImageOld = m_pImageOld; (*pSprite).m_pMaskDC = m_pMaskDC; (*pSprite).m_pMaskOld = m_pMaskOld; } else { ReleaseImageContext(); // release existing device contexts. ReleaseMaskContext(); } (*pSprite).m_pImage = m_pImage; } (*pSprite).m_pPalette = m_pPalette; if (m_pPalette != nullptr) { (*pSprite).m_bSharedPalette = true; (*pSprite).m_pPalImageOld = m_pPalImageOld; } (*pSprite).m_cRect = m_cRect; (*pSprite).m_cImageRect = m_cImageRect; (*pSprite).m_cSize = m_cSize; (*pSprite).m_cPosition = m_cPosition; (*pSprite).m_cMovementDelta = m_cMovementDelta; (*pSprite).m_cHotspot = m_cHotspot; (*pSprite).m_nType = m_nType; (*pSprite).m_pData = m_pData; (*pSprite).m_nZOrder = m_nZOrder; (*pSprite).m_nZPosition = m_nZPosition; (*pSprite).m_nCelID = m_nCelID; (*pSprite).m_nCelCount = m_nCelCount; (*pSprite).m_bVisible = m_bVisible; (*pSprite).m_bMasked = m_bMasked; (*pSprite).m_bMobile = m_bMobile; (*pSprite).m_bIntercepts = m_bIntercepts; (*pSprite).m_bRetainBackground = m_bRetainBackground; (*pSprite).m_bRetainContexts = m_bRetainContexts; (*pSprite).m_bAnimated = m_bAnimated; (*pSprite).m_bDuplicated = true; // mark it as a sprite with shared resources if (m_bVisible) { if (!m_bMasked || CreateMask(pDC)) { // create an image mask if needed (*pSprite).m_pMask = m_pMask; if (m_pPalette) (*pDC).SelectPalette(pPalOld, false); return true; } } else return true; } if (m_pPalette) (*pDC).SelectPalette(pPalOld, false); return false; } /************************************************************************* * * LoadSprite() * * Parameters: * CDC *pDC pointer to device context to be used in creating * a compatible context for the sprite's bitmap * * char *pszPathName pointer to text string with the fully qualified file * specification for the DIB bitmap file * * Return Value: * bool success/failure condition * * Description: Read in a DIB file and convert it to a DDB bitmap for use as * the sprite's image. A device context for the image is retained * if speed optimization has been specified. The DIB palette is * retained for use by the sprite if it does not yet have an * established palette. * ************************************************************************/ bool CSprite::LoadSprite(CDC *pDC, const char *pszPathName) { CDibDoc *myDib = nullptr; // pointer to our loaded DIB file bool bHavePalette = false; // whether or not we have a palette already CPalette *pPalOld = nullptr; // palette previously mapped to base context HPALETTE hPalette = nullptr; ClearImage(); // clear out any/all existing bitmaps, palettes, ClearMask(); // ... and device contexts myDib = new CDibDoc(); // create an object for our DIB m_pImage = new CBitmap(); // create an object for the sprite's image bHavePalette = (m_pPalette ? true : false); // loading a DIB automatically includes // ... loading its color palette, which // ... must be deleted if we already // ... have one assigned to this sprite if ((myDib != nullptr) && // verify we have the objects we just created (m_pImage != nullptr) && // .. and then attempt to open the requested (*myDib).OpenDocument(pszPathName)) { // .... bitmap file m_bVisible = true; if (!bHavePalette) // if we don't have a palette, then m_pPalette = (*myDib).DetachPalette(); // ... detach the DIB's palette for our use if (m_pPalette != nullptr) { pPalOld = (*pDC).SelectPalette(m_pPalette, false); (*pDC).RealizePalette(); hPalette = (HPALETTE)(*m_pPalette).m_hObject; } if (CreateImageContext(pDC)) { // Create a context for the image HDIB hDib = (*myDib).GetHDIB(); (*m_pImage).m_hObject = DIBtoBitmap( (*pDC).m_hDC, // convert the DIB to a DDB hPalette, hDib); // ... and store it in the sprite if ((*m_pImage).m_hObject != nullptr) { // verify the conversion was successful if (pPalOld != nullptr) (*pDC).SelectPalette(pPalOld, false); if (!m_bRetainContexts) // release the context if not optimizing ReleaseImageContext(); m_cSize = (*myDib).GetDocSize(); // ... size related variables m_cRect.right = m_cRect.left + m_cSize.cx; // ... update rectangular dimensions m_cRect.bottom = m_cRect.top + m_cSize.cy; m_cImageRect.SetRect(0, 0, m_cSize.cx, m_cSize.cy); m_nCelID = -1; m_nCelCount = 0; delete myDib; // discard the DIB myDib = nullptr; return true; // return success } } } if (pPalOld != nullptr) (*pDC).SelectPalette(pPalOld, false); ClearImage(); if (!bHavePalette && // delete the palette resource and object (m_pPalette != nullptr)) { // ... if it came from the DIB (*m_pPalette).DeleteObject(); delete m_pPalette; m_pPalette = nullptr; } if (myDib != nullptr) // lastly delete the DIB itself delete myDib; return false; // return failure } /************************************************************************* * * LoadSprite() * * Parameters: * CBitmap *pBitmap pointer to bitmap to be used and owned * CPalette *pPalette pointer to palette to be shared * * Return Value: * bool success/failure condition * * Description: use a CBitmap object as the sprite's image. A device context * for the image is retained if speed optimization has been specified. * The palette is retained for use by the sprite if it does not yet have an * established palette. * ************************************************************************/ bool CSprite::LoadSprite(CBitmap *pBitmap, CPalette *pPalette) { CSize mySize; BITMAP cBitmapData; if (pBitmap == nullptr) // punt if no bitmap return false; ClearImage(); // clear out any/all existing bitmaps, palettes, ClearMask(); // ... and device contexts m_pImage = pBitmap; // save pointer to bitmap if (pPalette != nullptr) // ... and the palette, if specified SharePalette(pPalette); (*pBitmap).GetObject(sizeof(BITMAP), &cBitmapData); // get the size of the bitmap m_cSize.cx = cBitmapData.bmWidth; // ... and initialize size variables m_cSize.cy = cBitmapData.bmHeight; m_cRect.right = m_cRect.left + m_cSize.cx; m_cRect.bottom = m_cRect.top + m_cSize.cy; m_cImageRect.SetRect(0, 0, m_cSize.cx, m_cSize.cy); m_nCelID = -1; m_nCelCount = 0; m_bVisible = true; return true; // return success } /************************************************************************* * * LoadResourceSprite() * * Parameters: * CDC *pDC pointer to device context to be used in creating * a compatible context for the sprite's bitmap * * int resId resource number for a bitmap identified in the game's * .RC resource file * * Return Value: * bool success/failure condition * * Description: Read in a DDB bitmap resource and store it in the sprite object. * Resource bitmaps do not have an accessible palette, so you * may need to set one directly (SetPalette or SharedPalette); * if none is specified, then the current system palette is used. * ************************************************************************/ bool CSprite::LoadResourceSprite(CDC *pDC, const int resId) { bool bSuccess = false; char chResID[16]; Common::sprintf_s(chResID, "#%d", resId); bSuccess = LoadResourceSprite(pDC, chResID); return bSuccess; // return failure } /************************************************************************* * * LoadResourceSprite() * * Parameters: * CDC *pDC pointer to device context to be used in creating * a compatible context for the sprite's bitmap * * char *pszPathName resource name for a bitmap identified in the game's * .RC resource file * * Return Value: * bool success/failure condition * * Description: Read in a DDB bitmap resource and store it in the sprite object. * Resource bitmaps do not have an accessible palette, so you * may need to set one directly (SetPalette or SharedPalette); * if none is specified, then the current system palette is used. * ************************************************************************/ bool CSprite::LoadResourceSprite(CDC *pDC, const char *pszName) { CDibDoc *myDib = nullptr; // pointer to our loaded DIB file bool bHavePalette = false; // whether or not we have a palette already CPalette *pPalOld = nullptr; // palette previously mapped to base context HPALETTE hPalette = nullptr; ClearImage(); // clear out any/all existing bitmaps, palettes, ClearMask(); // ... and device contexts myDib = new CDibDoc(); // create an object for our DIB m_pImage = new CBitmap(); // create an object for the sprite's image bHavePalette = (m_pPalette ? true : false); // loading a DIB automatically includes // ... loading its color palette, which // ... must be deleted if we already // ... have one assigned to this sprite if ((myDib != nullptr) && // verify we have the objects we just created (m_pImage != nullptr) && // .. and then attempt to open the requested (*myDib).OpenResourceDocument(pszName)) { // .... bitmap file m_bVisible = true; if (!bHavePalette) // if we don't have a palette, then m_pPalette = (*myDib).DetachPalette(); // ... detach the DIB's palette for our use if (m_pPalette != nullptr) { pPalOld = (*pDC).SelectPalette(m_pPalette, false); (*pDC).RealizePalette(); hPalette = (HPALETTE)(*m_pPalette).m_hObject; } if (CreateImageContext(pDC)) { // Create a context for the image HDIB hDib = (*myDib).GetHDIB(); (*m_pImage).m_hObject = DIBtoBitmap((*pDC).m_hDC, // convert the DIB to a DDB hPalette, hDib); // ... and store it in the sprite if ((*m_pImage).m_hObject != nullptr) { // verify the conversion was sucessfull if (pPalOld != nullptr) (*pDC).SelectPalette(pPalOld, false); if (!m_bRetainContexts) // release the context if not optimizing ReleaseImageContext(); m_cSize = (*myDib).GetDocSize(); // ... size related variables m_cRect.right = m_cRect.left + m_cSize.cx; // ... update rectangular dimensions m_cRect.bottom = m_cRect.top + m_cSize.cy; m_cImageRect.SetRect(0, 0, m_cSize.cx, m_cSize.cy); m_nCelID = -1; m_nCelCount = 0; delete myDib; // discard the DIB myDib = nullptr; return true; // return success } } } if (pPalOld != nullptr) (*pDC).SelectPalette(pPalOld, false); ClearImage(); if (!bHavePalette && // delete the palette resource and object (m_pPalette != nullptr)) { // ... if it came from the DIB (*m_pPalette).DeleteObject(); delete m_pPalette; m_pPalette = nullptr; } if (myDib != nullptr) // lastly delete the DIB itself delete myDib; return false; // return failure } /************************************************************************* * * LoadCels() * * Parameters: * CDC *pDC pointer to device context to be used in creating * a compatible context for the sprite's cel bitmap * char *pszPathName pointer to text string with the fully qualified file * specification for the DIB bitmap file * int nCels number of cels in strip * * Return Value: * bool success/failure condition * * Description: Read in a DIB file and convert it to a DDB bitmap for use as * the sprite's cels. * ************************************************************************/ bool CSprite::LoadCels(CDC *pDC, const char *pszPathName, const int nCels) { int nOldCelCount; nOldCelCount = m_nCelCount; // retain previous cel count if (LoadSprite(pDC, pszPathName) && // load the cel strip as if a normal SetupCels(nCels)) // ... sprite image, then set specifics if (nOldCelCount == 0) // if there wasn't a previous cel strip m_bAnimated = true; // ... then set for initially animated return true; return false; } /************************************************************************* * * LoadCels() * * Parameters: * CBitmap *pBitmap pointer to bitmap to be used and owned * int nCels number of cels in strip * CPalette *pPalette pointer to palette to be shared * * Return Value: * bool success/failure condition * * Description: Read in a DIB file and convert it to a DDB bitmap for use as * the sprite's cels. * ************************************************************************/ bool CSprite::LoadCels(CBitmap *pBitmap, const int nCels, CPalette *pPalette) { int nOldCelCount; nOldCelCount = m_nCelCount; // retain previous cel count if (LoadSprite(pBitmap, pPalette) && // load the cel strip as if a normal SetupCels(nCels)) // ... sprite image, then set specifics if (nOldCelCount == 0) // if there wasn't a previous cel strip m_bAnimated = true; // ... then set for initially animated return true; return false; } /************************************************************************* * * LoadResourceCels() * * Parameters: * CDC *pDC pointer to device context to be used in creating * a compatible context for the sprite's cel bitmap * int resId resource number for a bitmap identified in the game's * int nCels number of cels in strip * .RC resource file * * Return Value: * bool success/failure condition * * Description: Read in a DIB file and convert it to a DDB bitmap for use as * the sprite's cels. * ************************************************************************/ bool CSprite::LoadResourceCels(CDC *pDC, const int resId, const int nCels) { bool bSuccess = false; char chResID[16]; Common::sprintf_s(chResID, "#%d", resId); bSuccess = LoadResourceCels(pDC, chResID, nCels); return bSuccess; // return failure } /************************************************************************* * * LoadResourceCels() * * Parameters: * CDC *pDC pointer to device context to be used in creating * a compatible context for the sprite's cel bitmap * char *pszPathName resource name for a bitmap identified in the game's * .RC resource file * int nCels number of cels in strip * * Return Value: * bool success/failure condition * * Description: Read in a DIB file and convert it to a DDB bitmap for use as * the sprite's cels. * ************************************************************************/ bool CSprite::LoadResourceCels(CDC *pDC, const char *pszName, const int nCels) { int nOldCelCount; nOldCelCount = m_nCelCount; // retain previous cel count if (LoadResourceSprite(pDC, pszName) && // load the cel strip as if a normal SetupCels(nCels)) // ... sprite image, then set specifics if (nOldCelCount == 0) // if there wasn't a previous cel strip m_bAnimated = true; // ... then set for initially animated return true; return false; } /************************************************************************* * * SetupCels() * * Parameters: none * * Return Value: * bool success/failure condition * * Description: initialize cel animation strip variables and validate * strip length * ************************************************************************/ bool CSprite::SetupCels(const int nCels) { int nStripWidth; // temp place to hold cel strip width m_nCelID = -1; // no current cel m_nCelCount = nCels; // set cel count nStripWidth = m_cSize.cx; // retain cell strip pixel length m_cSize.cx /= nCels; // calculate width of a cel if (m_cSize.cx * nCels == nStripWidth) { // verify we have an even multiple m_cRect.right = m_cRect.left + m_cSize.cx; // reset sprite rectangular bounds m_cRect.bottom = m_cRect.top + m_cSize.cy; // ... based on cel dimensions m_cImageRect.SetRect(0, 0, m_cSize.cx, m_cSize.cy); // set bounds for first cel in strip return true; } return false; } /************************************************************************* * * SetPalette() * * Parameters: * CPalette *pPalette color palette to be associated with the sprite * * Return Value: * bool success/failure condition * * Description: Set to use the specified palette when dealing with this sprite. * If a palette has been previously associated with the sprite, * then map it out of the various contexts associated with the * sprite, then map in the new one. * ************************************************************************/ bool CSprite::SetPalette(CPalette *pPalette) { if (m_pImageDC != nullptr) { // if we already have a palette we must if (m_pImage != nullptr) // ... first map out any existing image (*m_pImageDC).SelectObject(m_pImageOld); // ... then map out the old palette if (m_pPalette != nullptr) (*m_pImageDC).SelectPalette(m_pPalImageOld, false); } if (m_pBackgroundDC != nullptr) { // similarly we need to map out our background if (m_pBackground != nullptr) // ... bitmap as well (*m_pBackgroundDC).SelectObject(m_pBackgroundOld); if (m_pPalette != nullptr) // .. then map out the palette (*m_pBackgroundDC).SelectPalette(m_pPalBackOld, false); } ClearPalette(); // release existing palette m_pPalette = pPalette; // set the new palette if (m_pImageDC == nullptr) // done if no context to map it into return true; m_pPalImageOld = (*m_pImageDC).SelectPalette(m_pPalette, false); // map in the new palette and then (*m_pImageDC).RealizePalette(); // ... tell the system to use it if (m_pImage != nullptr) { m_pImageOld = (*m_pImageDC).SelectObject(m_pImage); // map in our image bitmap if it exists if (m_pImageOld == nullptr) return false; } if (m_pBackgroundDC == nullptr) // done if no background context return true; m_pPalBackOld = (*m_pBackgroundDC).SelectPalette(m_pPalette, false); // map in the new palette and then (*m_pBackgroundDC).RealizePalette(); // ... tell the system to use it if (m_pBackground != nullptr) { m_pBackgroundOld = (*m_pBackgroundDC).SelectObject(m_pBackground); // map in our background if (m_pBackgroundOld == nullptr) return false; } return true; } /************************************************************************* * * SharePalette() * * Parameters: * CPalette *pPalette color palette to be associated with the sprite * * Return Value: * bool success/failure condition * * Description: Use the specified palette when dealing with this sprite, * and mark it as a shared resource; hence don't delete it * when later changing the palette or destroying the sprite. * If a palette has been previously associated with the sprite, * then map it (and its bitmap) out of the device context, * delete it if it is not a shared resource, map the new * palette into the context, and then map in the bitmap. * ************************************************************************/ bool CSprite::SharePalette(CPalette *pPalette) { if (SetPalette(pPalette)) { // load a new palette into our sprite m_bSharedPalette = true; // ... and mark it as a shared resource return true; } // ... whioh means it continues to exist // ... when this sprite is later destroyed m_pPalette = nullptr; // not able to do that so ensure the return false; // ... pointer is nulled } /************************************************************************* * * PaintSprite() * * Parameters: * CDC *pDC pointer to device context for the destination * of the painting operation (e.g. display screen) * * int x coordinate which identifies where to begin painting * int y the sprite, and which corresponds to the upper lefthand * corner location of the sprite's bitmap * * Return Value: * bool success/failure condition * * Description: Paint the sprite. If it is a mobile sprite, then save * the bitmap area it will cover up so that when it moves * again so the saved bitmap area can be painted back to the * screen to correctly re-establish the background. Background * restoration is either straightforward, or complex, depending * on whether or not the sprite overlaps other sprites. * ************************************************************************/ bool CSprite::PaintSprite(CDC *pDC, const int x, const int y) { bool bSuccess = false; CRect overlapRect, dstRect; CSprite *pOverlap = nullptr; m_bTouchedSprite = false; // default to no sprite being overlapped by this m_pTouchedSprite = nullptr; // ... painting operation if (!m_bPositioned) // setup the initial location if not SetPosition(x, y); if (!m_bVisible) { // just place it if invisible SetPosition(x, y); return true; } if (pDC == nullptr) // punt if no device context for the sprite return false; if (m_bAnimated && m_nCelCount) // advance to the next cel in the strip UpdateCel(); dstRect.SetRect(x, y, x + m_cSize.cx, y + m_cSize.cy); // calculate destination rectangle pOverlap = Interception(&dstRect); // see if the sprite will intercept another if (pOverlap != nullptr) { // ... and if so, record that fact m_bTouchedSprite = true; // we've touched something m_pTouchedSprite = pOverlap; // here's what we touched } if (!m_bLinked || // how we do the painting depends on whether (!m_bOverlaps && (pOverlap == nullptr))) { // ... we intercept another sprite if ((m_bHaveBackdrop || (m_pBackground != nullptr)) && overlapRect.IntersectRect(&m_cRect, &dstRect)) { // ... optimize painting of localized movement bSuccess = DoOptimizedPainting(pDC, &dstRect); // ... by doing it all offscreen SetPosition(x, y); // establish the sprite's new position } else { if (m_bHaveBackdrop || (m_pBackground != nullptr)) { bSuccess = RefreshBackground(pDC); // If it isn't close, just restore its background if (!bSuccess) return false; } SetPosition(x, y); // formally set the sprites new location if (!m_bHaveBackdrop && m_bRetainBackground) { bSuccess = SaveBackground(pDC); // save the background art of its new location if (!bSuccess) return false; } bSuccess = UpdateSprite(pDC); // paint the sprite in its new location } m_bOverlaps = false; // this sprite does not overlap another m_nZPosition = m_nZOrder; // ... so reset its z ordering } else { // we have an overlap, so we need to do tear down bSuccess = DoOverlapPainting(pDC, &dstRect); // ... and rebuild sprite by sprite SetPosition(x, y); // now establish the sprite's new position } return bSuccess; } /************************************************************************* * * SetCel() * * Parameters: * int nCelID index of the desired cell * * Return Value: * bool success/failure condition * * Description: advance the cel image to the specified bitmap in the strip. * ************************************************************************/ void CSprite::SetCel(const int nCelID) { m_nCelID = nCelID; while (m_nCelID >= m_nCelCount) { m_nCelID -= m_nCelCount; } m_cImageRect.left = m_nCelID * m_cSize.cx; m_cImageRect.right = m_cImageRect.left + m_cSize.cx; } /************************************************************************* * * UpdateSprite() * * Parameters: * CDC *pDC pointer to device context for the destination * of the painting operation (e.g. display screen) * * Return Value: * bool success/failure condition * * Description: Repaint the sprite in its current location. If it is a * mobile sprite without a saved background bitmap, then it * will be created and saved for later use. * * NOTE: use PaintSprite() to paint a sprite to new location. * ************************************************************************/ bool CSprite::UpdateSprite(CDC *pDC) { CPalette *pPalOld = nullptr; bool bSuccess = false; if (!m_bVisible) // punt if not visible return true; if (pDC == nullptr) // verify we have the necessary device contexts return false; if (!m_bHaveBackdrop && m_bRetainBackground && // if we don't have a bitmap backdrop, and if (m_pBackgroundDC == nullptr)) { // ... we move and are painting for the first time if (!SaveBackground(pDC)) // ... and then load it with the proper image return false; } if (m_pPalette != nullptr) { pPalOld = (*pDC).SelectPalette(m_pPalette, false); // map in a palette if present (*pDC).RealizePalette(); } // ... and tell the system we did that if (m_bMasked) // if masked, then paint the sprite, masking out bSuccess = DoSpritePainting(pDC, m_cPosition); // ... (ignoring) pure white pixels else { bSuccess = SetupImage(pDC); // not masked - so just splat the entire bitmap if (bSuccess) { bSuccess = (*pDC).BitBlt(m_cPosition.x, m_cPosition.y, m_cSize.cx, m_cSize.cy, m_pImageDC, m_cImageRect.left, m_cImageRect.top, SRCCOPY); if (!m_bRetainContexts) ReleaseImageContext(); } } if (m_pPalette != nullptr) // map back the previous palette if needed (*pDC).SelectPalette(pPalOld, false); return bSuccess; } /************************************************************************* * * DoSpritePainting() * * Parameters: * CDC *pDC pointer to device context for the destination * of the painting operation (e.g. display screen) * * Return Value: * bool success/failure condition * * Description: Paint the source bitmap into the destination bitmap. * White pixels in the source bitmap are masked out (not painted) * thereby allowing irregular shaped images to be overlayed onto * a background. Painting is done in an offscreen bitmap and * then transfered to the destination context; this reduces sprite * flicker, albeit at the cost of addition time. * ************************************************************************/ bool CSprite::DoSpritePainting(CDC * pDC, CPoint cPoint) { CDC *pDC2 = nullptr; // context for manipulating background image CBitmap *pBitmap2 = nullptr, // bitmap for pDC2 *pBitmap2Old = nullptr; // bitmap previously mapped into pDC2 CPalette *pPal2Old = nullptr; // palette previously mapped into pDC2 bool bSuccess = false; // success/failure if (!m_bVisible) // punt if not visible return true; if (pDC == nullptr) // punt if we don't have contexts return false; if (SetupImage(pDC) && // put the image and mask bitmaps into SetupMask(pDC)) { // ... device contexts pDC2 = new CDC(); // create our temporary bitmap work areas pBitmap2 = new CBitmap(); if ((pDC2 != nullptr) && (pBitmap2 != nullptr) && (*pDC2).CreateCompatibleDC(pDC)) { if (m_pPalette != nullptr) { // map in a palette if available pPal2Old = (*pDC2).SelectPalette(m_pPalette, false); // ... and for the background work area (*pDC2).RealizePalette(); } // ... make it real too if ((*pBitmap2).CreateCompatibleBitmap(pDC, m_cSize.cx, m_cSize.cy)) { pBitmap2Old = (*pDC2).SelectObject(pBitmap2); if (pBitmap2Old != nullptr) { if (m_bHaveBackdrop) { // get background image, either from if (GetBackdropDC(pDC) == nullptr) // .. the backdrop or the sprite itselt goto punt; (*pDC2).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, m_pBackdropDC, cPoint.x, cPoint.y, SRCCOPY); ReleaseBackdropDC(); } else (*pDC2).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, pDC, cPoint.x, cPoint.y, SRCCOPY); // get the sprite's background image (*pDC2).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, m_pMaskDC, m_cImageRect.left, m_cImageRect.top, SRCAND); // ... mask out where sprite will go (*pDC2).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, m_pImageDC, m_cImageRect.left, m_cImageRect.top, SRCPAINT); // combine image with background bSuccess = (*pDC).BitBlt(cPoint.x, cPoint.y, m_cSize.cx, m_cSize.cy, pDC2, 0, 0, SRCCOPY); // paint result to the screen } } } punt: if (pBitmap2Old != nullptr) // now release the temporary resources we used (*pDC2).SelectObject(pBitmap2Old); if (pBitmap2 != nullptr) { (*pBitmap2).DeleteObject(); delete pBitmap2; } if (pPal2Old != nullptr) (*pDC2).SelectPalette(pPal2Old, false); if (pDC2 != nullptr) { (*pDC2).DeleteDC(); delete pDC2; } } if (!m_bRetainContexts) { // release the sprite contexts if not optimizing ReleaseImageContext(); ReleaseMaskContext(); } return bSuccess; // return success or failure } /************************************************************************* * * DoOptimizedPainting() * * Parameters: * CDC *pDC pointer to device context for the destination * of the painting operation (e.g. display screen) * * CRect *pDstRect rectangle defining the area where the sprite will be * * Return Value: * bool success/failure condition * * Description: Determine the area affected by painting the sprite in a * new location (e.g. old area where the background bitmap * needs to be restored, as well as the area where the sprite * is to be newly painted), and then do all of that painting * in a compatible offscreen bitmap. Once that is accomplished, * paint the offscreen bitmap onto the destination device context; * This elimates very noticeable sprite "flicker" that would * otherwise occur, albeit at the cost of additional time. * ************************************************************************/ bool CSprite::DoOptimizedPainting(CDC *pDC, CRect *pDstRect) { bool bSuccess = false; // success/failure return status CPoint cPoint; // where to paint sprite in the work area CRect unionRect; // rectangle enclosing old and new sprite locations CDC workDC; // device context for offscreen work area CBitmap *pBitmap = nullptr, // bitmap for the work area *pBitmapOldWork = nullptr; // bitmap previously mapped to work area's context CPalette *pPalOld = nullptr, // palette previously mapped to destination context *pPalOldWork = nullptr; // palette prevously mapped to work area's context int dx, dy; // delta sizes of work area's bitmap if (pDC == nullptr) // punt if no sprite device context return false; if (m_pPalette != nullptr) { // if there is a palette pPalOld = (*pDC).SelectPalette(m_pPalette, false); // ... select it into the context (*pDC).RealizePalette(); } // ... and tell the system about it unionRect = m_cRect; // the work area is defined by our sprite if (pDstRect != nullptr) unionRect.UnionRect(unionRect, pDstRect); // calculate the smallest enclosing rectangle that dx = unionRect.right - unionRect.left; // ... contains the bitmap area where the sprite was dy = unionRect.bottom - unionRect.top; // ... and the bitmap area where it will be next pBitmap = new CBitmap(); // create an offscreen bitmap where we do all the if (pBitmap == nullptr) // ... work; first create a bitmap for the enclosing return false; // ... rectangle, and if that fails, then punt if (!(*pBitmap).CreateCompatibleBitmap(pDC, dx, dy)) { delete pBitmap; return false; } if (workDC.CreateCompatibleDC(pDC)) { // setup the work area context if (m_pPalette != nullptr) { // if we have a palette, map it into the work context pPalOldWork = workDC.SelectPalette(m_pPalette, false); workDC.RealizePalette(); } // ... and tell the system to use it pBitmapOldWork = workDC.SelectObject(pBitmap); // now map in the bitmap into that context if (pBitmapOldWork != nullptr) { // next copy the image enclosed by the rectangle if (m_bHaveBackdrop) { // restore the background where the sprite was if (GetBackdropDC(pDC) == nullptr) goto punt; bSuccess = workDC.BitBlt(0, 0, dx, dy, m_pBackdropDC, unionRect.left, unionRect.top, SRCCOPY); ReleaseBackdropDC(); if (!bSuccess) goto punt; } else { workDC.BitBlt(0, 0, dx, dy, pDC, unionRect.left, unionRect.top, SRCCOPY); // ... into the bitmap if (SetupBackground(pDC)) { workDC.BitBlt(m_cPosition.x - unionRect.left, // restore the background where the sprite was m_cPosition.y - unionRect.top, m_cSize.cx, m_cSize.cy, m_pBackgroundDC, 0, 0, SRCCOPY); (*m_pBackgroundDC).BitBlt(0, // save the background where the sprite will be 0, m_cSize.cx, m_cSize.cy, &workDC, (*pDstRect).left - unionRect.left, (*pDstRect).top - unionRect.top, SRCCOPY); if (!m_bRetainContexts || m_bDuplicated) // release background context if not optimizing ReleaseBackgroundContext(); } else goto punt; } cPoint.x = (*pDstRect).left - unionRect.left; // determine where to paint the new sprite image cPoint.y = (*pDstRect).top - unionRect.top; // ... into the work area bSuccess = ((!m_bMasked || SetupMask(pDC)) && // setup the contexts we need SetupImage(pDC)); if (bSuccess) { if (m_bMasked) { // need to do a masked copy ... workDC.BitBlt(cPoint.x, // ... mask out where sprite goes cPoint.y, m_cSize.cx, m_cSize.cy, m_pMaskDC, m_cImageRect.left, m_cImageRect.top, SRCAND); workDC.BitBlt(cPoint.x, // ... paint in the sprite cPoint.y, m_cSize.cx, m_cSize.cy, m_pImageDC, m_cImageRect.left, m_cImageRect.top, SRCPAINT); } else // just paint in the sprite workDC.BitBlt(cPoint.x, cPoint.y, m_cSize.cx, m_cSize.cy, m_pImageDC, m_cImageRect.left, m_cImageRect.top, SRCCOPY); if (!m_bRetainContexts) { // release contexts if not optimizing ReleaseImageContext(); if (m_bMasked) ReleaseMaskContext(); } } if (bSuccess) { (*pDC).BitBlt(unionRect.left, unionRect.top, dx, dy, &workDC, 0, 0, SRCCOPY); workDC.SelectObject(pBitmapOldWork); // tear down the work area's bitmap, palette if (m_pPalette != nullptr) { // ... and device context, as well as the workDC.SelectPalette(pPalOldWork, false); // ... destination's palette (*pDC).SelectPalette(pPalOld, false); } workDC.DeleteDC(); (*pBitmap).DeleteObject(); delete pBitmap; return bSuccess; } } } punt: if (pBitmapOldWork != nullptr) // we failed, so tear down the resources used workDC.SelectObject(pBitmapOldWork); // ... map out the bitmap if (pPalOldWork != nullptr) // ... map out the palette workDC.SelectPalette(pPalOldWork, false); if (workDC.m_hDC != nullptr) // ... release the context workDC.DeleteDC(); (*pBitmap).DeleteObject(); // ... delete the bitmap delete pBitmap; if (pPalOld != nullptr) // map out the palette from the output context (*pDC).SelectPalette(pPalOld, false); return false; } /************************************************************************* * * DoOverlapPainting() * * Parameters: * CDC *pDC pointer to device context for the destination * of the painting operation (e.g. display screen) * CRect *myRect new area where the sprite will be painted * CSprite *pSprite pointer to intercepted sprite in chain * * Return Value: * bool success/failure condition * * Description: Paint a sprite into its new location, after restoring * the background where it is currently located. The sprite * is assumed to be part of a set of overlapping sprites, so * sprite-by-sprite reconstruction of the background is needed. * ************************************************************************/ bool CSprite::DoOverlapPainting(CDC *pDC, CRect *myRect) { CPalette *pPalOld = nullptr; bool bSuccess = false; if (pDC == nullptr) // punt if no context return false; if (m_pPalette != nullptr) { // if there is a palette pPalOld = (*pDC).SelectPalette(m_pPalette, false); // ... select it into the context (*pDC).RealizePalette(); } // ... and tell the system about it bSuccess = ReconstructBackground(pDC, myRect); // go do the actual painting if (m_pPalette != nullptr) // map out the palette (*pDC).SelectPalette(pPalOld, false); return bSuccess; } /************************************************************************* * * RefreshBackground() * * Parameters: * CDC *pDC pointer to device context for the destination * of the painting operation (e.g. display screen) * * Return Value: * bool success/failure condition * * Description: Paint a sprite's saved background bitmap into its * appropriate area. * ************************************************************************/ bool CSprite::RefreshBackground(CDC *pDC) { CPalette *pPalOld = nullptr; bool bSuccess = false; if (!m_bVisible) // punt if not visible return true; if (pDC == nullptr) // punt if no sprite device context return false; if (!m_bHaveBackdrop && m_pBackground == nullptr) // done if no saved background return true; if (m_pPalette != nullptr) { // map a palette to the destination context pPalOld = (*pDC).SelectPalette(m_pPalette, false); // ... if available (*pDC).RealizePalette(); } if (m_bOverlaps) // paint the background bitmap to the device bSuccess = ReconstructBackground(pDC, nullptr); // ... from all the sprites that overlap it else if (m_bHaveBackdrop) { if (GetBackdropDC(pDC) == nullptr) bSuccess = false; else { bSuccess = (*pDC).BitBlt(m_cPosition.x, m_cPosition.y, m_cSize.cx, m_cSize.cy, m_pBackdropDC, m_cPosition.x, m_cPosition.y, SRCCOPY); ReleaseBackdropDC(); } } else { bSuccess = SetupBackground(pDC); // paint the background bitmap to the device if (bSuccess) { bSuccess = (*pDC).BitBlt(m_cPosition.x, m_cPosition.y, m_cSize.cx, m_cSize.cy, m_pBackgroundDC, 0, 0, SRCCOPY); if (!m_bRetainContexts || m_bDuplicated) ReleaseBackgroundContext(); } } if (m_pPalette != nullptr) // map out the palette from the destination (*pDC).SelectPalette(pPalOld, false); m_bPositioned = false; // no real position now m_nZPosition = m_nZOrder; // reset z ordering return bSuccess; } /************************************************************************* * * SaveBackground() * * Parameters: * CDC *pDC pointer to device context where the sprite will be painted * * Return Value: * bool success/failure condition * * Description: Fetch the area into which the sprite will be painted and * save it as a bitmap. It can then be repainted there later * to restore the background. * ************************************************************************/ bool CSprite::SaveBackground(CDC *pDC) { CPalette *pPalOld = nullptr; bool bSuccess = false; if (m_bHaveBackdrop || !m_bRetainBackground) // not valid to save background with backdrop return false; // ... or if not retaining same if (!m_bVisible) // punt if not visible return true; if (pDC == nullptr) // punt if no output context return false; if (!CreateBackground(pDC)) // create background context return false; if (m_pPalette != nullptr) { // map a palette to the destination context pPalOld = (*pDC).SelectPalette(m_pPalette, false); (*pDC).RealizePalette(); } bSuccess = SetupBackground(pDC); // grab the background bitmap if (bSuccess) { bSuccess = (*m_pBackgroundDC).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, pDC, m_cPosition.x, m_cPosition.y, SRCCOPY); if (!m_bRetainContexts || m_bDuplicated) // release context if not optimizing ReleaseBackgroundContext(); } if (m_pPalette != nullptr) // map out palette from destination context (*pDC).SelectPalette(pPalOld, false); return bSuccess; } /************************************************************************* * * ReconstructBackground() * * Parameters: * CDC *pDC pointer to device context where the sprite will be painted * CRect *myRect nullptr or pointer to new area where sprite gets painted * CSprite *pSprite pointer to intercepted sprite in chain * * Return Value: * bool success/failure condition * * Description: Reconstruct the background image by restoring the background * bitmaps from all overlapping sprites, working from topmost * down to including our sprite. Then paint all those sprite's * images (excluding ourself) onto the restore background, thus * creating a bitmap that no longer contains our sprite's image. * Lastly, if we have a pointer to a rectangle, then paint our * sprite image into it, since that is its new location. * ************************************************************************/ bool CSprite::ReconstructBackground(CDC *pDC, CRect *myRect) { CDC workDC; // context for our work area CSprite *pSprite = nullptr, // various sprite pointers *pTestSprite, *pZHead; bool bDoingTopMost; // special case for single sprite that is topmost bool bSuccess = false; // used to hold success/failure CRect baseRect, // rectangle spanning the sprite update unionRect, // rectangle spanning all sprites touched overlapRect; // temporary rectangle information int dx, dy; // size of unionRect and work bitmap CPoint cPoint; // temporary point information CBitmap *pBitmap = nullptr, // work area bitmap *pBitmapOld = nullptr; // bitmap previously selected to work context CPalette *pPalOld = nullptr, // palette previously selected to display context *pPalOldWork = nullptr; // palette previously selected to work context #if SPRITE_DEBUG CRect clipRect; // current clipping area CPoint viewOrigin; // current viewport origin int nClipResult; (*pDC).GetClipBox(&clipRect); nClipResult = (*pDC).SelectClipRgn(nullptr); (*pDC).GetClipBox(&clipRect); viewOrigin = (*pDC).GetViewportOrg(); viewOrigin.x = - viewOrigin.x; viewOrigin.y = - viewOrigin.y; #endif unionRect = m_cRect; // the work area is defined by our sprite if (myRect != nullptr) // include the area of our new destination unionRect.UnionRect(unionRect, myRect); // ... if specified, and retain it for later baseRect = unionRect; pSprite = this; // start processing from this sprite and make (*pSprite).m_pZNext = nullptr; // ... it be the first in the z order chain (*pSprite).m_pZPrev = nullptr; pTestSprite = m_pSpriteChain; // set all sprites to not having been tested while (pTestSprite != nullptr) { // ... for this reconstruction cycle if ((*pTestSprite).m_bVisible && (*pTestSprite).m_bPositioned) (*pTestSprite).m_bOverlapTest = false; else (*pTestSprite).m_bOverlapTest = true; pTestSprite = (*pTestSprite).m_pNext; } pTestSprite = m_pSpriteChain; // get first sprite to test against m_bOverlapTest = true; // set to not test against the first sprite m_bOverlaps = true; if (m_bHaveBackdrop || (m_pBackground != nullptr)) m_bPaintOverlap = true; else m_bPaintOverlap = false; while (pTestSprite != nullptr) { // look for sprites we overlap or which are if (!(*pTestSprite).m_bOverlapTest && // ... overlapped by sprites that overlap us overlapRect.IntersectRect(&unionRect, &(*pTestSprite).m_cRect)) { unionRect.UnionRect(unionRect, (*pTestSprite).m_cRect); // expand rectangle to encompass it (*pTestSprite).m_bOverlapTest = true; // mark it as having been tested (*pTestSprite).m_bOverlaps = true; // mark it as involved in an overlap situation if ((m_bHaveBackdrop || // won't paint sprites without a background ((*pTestSprite).m_pBackground != nullptr)) && overlapRect.IntersectRect(&baseRect, &(*pTestSprite).m_cRect)) (*pTestSprite).m_bPaintOverlap = true; else (*pTestSprite).m_bPaintOverlap = false; while (true) { // insert the sprite in the sorted z chain if ((*pTestSprite).m_nZPosition < (*pSprite).m_nZPosition) { // need to head leftward if (((*pSprite).m_pZPrev == nullptr) || // put it to the left of us (i.e. overlaps us) ((*(*pSprite).m_pZPrev).m_nZPosition < (*pTestSprite).m_nZPosition)) { (*pTestSprite).m_pZPrev = (*pSprite).m_pZPrev; (*pTestSprite).m_pZNext = pSprite; (*pSprite).m_pZPrev = pTestSprite; if ((*pTestSprite).m_pZPrev != nullptr) (*(*pTestSprite).m_pZPrev).m_pZNext = pTestSprite; break; } else pSprite = (*pSprite).m_pZPrev; // shift left in chain and set to test again } else // need to head rightward if ((*pTestSprite).m_nZPosition >= (*pSprite).m_nZPosition) { if (((*pSprite).m_pZNext == nullptr) || // put it to the right of us (we overlap it) ((*(*pSprite).m_pZNext).m_nZPosition >= (*pTestSprite).m_nZPosition)) { (*pTestSprite).m_pZNext = (*pSprite).m_pZNext; (*pTestSprite).m_pZPrev = pSprite; (*pSprite).m_pZNext = pTestSprite; if ((*pTestSprite).m_pZNext != nullptr) (*(*pTestSprite).m_pZNext).m_pZPrev = pTestSprite; break; } else pSprite = (*pSprite).m_pZNext; // shift right in chain and set to test again } } pTestSprite = m_pSpriteChain; // need to check all sprites again since the } // ... the bounding rectangle has expanded and else // ... may now intercept additional sprites pTestSprite = (*pTestSprite).m_pNext; // set to test against the next sprite } while ((*pSprite).m_pZPrev != nullptr) // find the head of the z order chain so that pSprite = (*pSprite).m_pZPrev; // ... we can begin background reconstruction pZHead = pSprite; // save head of z order chain if (((*pZHead).m_nId == m_nId) && // if we are the head of the z order chain (m_nZOrder == SPRITE_TOPMOST) && // ... and we are a top-most type of sprite !m_bHaveBackdrop) { // ... and we're painting direct to the screen pTestSprite = (*pZHead).m_pZNext; // ... then we only need to handle this sprite while (pTestSprite != nullptr) { // so set to not paint others in z order chain (*pTestSprite).m_bPaintOverlap = false; pTestSprite = (*pTestSprite).m_pZNext; } if (m_nCelCount <= 1) // if we're not a cel strip, then set a bDoingTopMost = true; // ... special flag indicating just our sprite else bDoingTopMost = false; // ... otherwise use default methodology unionRect = baseRect; // minimize the bounding rectangle } else bDoingTopMost = false; dx = unionRect.right - unionRect.left; // get the width and height of the work area dy = unionRect.bottom - unionRect.top; // ... spanned by all overlapping sprites pBitmap = new CBitmap(); // create a bitmap to contain our work area if (pBitmap == nullptr) return false; if (!(*pBitmap).CreateCompatibleBitmap(pDC, dx, dy)) { delete pBitmap; return false; } if (workDC.CreateCompatibleDC(pDC)) { // create a device context to contain our if (m_pPalette != nullptr) { // ... work area and map in the palette pPalOldWork = workDC.SelectPalette(m_pPalette, false); workDC.RealizePalette(); pPalOld = (*pDC).SelectPalette(m_pPalette, false); (*pDC).RealizePalette(); } pBitmapOld = workDC.SelectObject(pBitmap); // map our bitmap into the work area if (pBitmapOld != nullptr) { // now need to create the original background if (m_bHaveBackdrop) { // ... which is easy if we are maintaining an if (GetBackdropDC(pDC) == nullptr) // ... offscreen bitmap for the whole screen bSuccess = false; else { bSuccess = workDC.BitBlt(0, // copy the background section into the work area 0, dx, dy, m_pBackdropDC, unionRect.left, unionRect.top, SRCCOPY); ReleaseBackdropDC(); } if (!bSuccess) goto punt; while (true) { pTestSprite = (*pSprite).m_pZNext; // now update z order relative positions if (pTestSprite == nullptr) // ... of every sprite in the z order chain break; // ... to ensure/maintain increasing ordering if ((*pSprite).m_nZOrder == (*pTestSprite).m_nZOrder) (*pTestSprite).m_nZPosition = (*pSprite).m_nZPosition + 1; pSprite = pTestSprite; } } else { // no backdrop, so first grab the screen bSuccess = workDC.BitBlt(0, 0, dx, dy, pDC, unionRect.left, unionRect.top, SRCCOPY); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY); #endif while (true) { // now restore the saved background from each if ((*pSprite).m_bPaintOverlap && (*pSprite).m_bRetainBackground) { bSuccess = (*pSprite).SetupBackground(pDC); // ... sprite in the z order chain if (bSuccess) { if ((*pSprite).m_bMasked && // if just doing the single topmost sprite, then bDoingTopMost && // ... replace its image area with its background (*pSprite).SetupMask(pDC)) { // .this is extra work but compensated by ignoring all others bSuccess = workDC.BitBlt((*pSprite).m_cPosition.x - unionRect.left, // mask away where sprite image is located (*pSprite).m_cPosition.y - unionRect.top, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, SRCAND); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY); bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, 0, 0, SRCCOPY); #endif bSuccess = (*(*pSprite).m_pMaskDC).BitBlt( // invert the image mask (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, NOTSRCCOPY); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, 0, 0, SRCCOPY); #endif bSuccess = (*(*pSprite).m_pBackgroundDC).BitBlt(0, 0, // mask away area of saved background that (*pSprite).m_cSize.cx, // ... is NOT spanned by the sprite image (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, SRCAND); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pBackgroundDC, 0, 0, SRCCOPY); #endif bSuccess = (*(*pSprite).m_pMaskDC).BitBlt( (*pSprite).m_cImageRect.left, // put the mask back the way it was (*pSprite).m_cImageRect.top, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, NOTSRCCOPY); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, 0, 0, SRCCOPY); #endif bSuccess = workDC.BitBlt((*pSprite).m_cPosition.x - unionRect.left, // paint the remaining background into cleared area (*pSprite).m_cPosition.y - unionRect.top, // i.e. we only updated the pixels where the image (*pSprite).m_cSize.cx, // ... was actually located, leaving all else untouched (*pSprite).m_cSize.cy, (*pSprite).m_pBackgroundDC, 0, 0, SRCPAINT); } else // just splat sprite's background to work area bSuccess = workDC.BitBlt((*pSprite).m_cPosition.x - unionRect.left, (*pSprite).m_cPosition.y - unionRect.top, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pBackgroundDC, 0, 0, SRCCOPY); if (!(*pSprite).m_bRetainContexts) // release contexts if not optimizing (*pSprite).ReleaseMaskContext(); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY); #endif } if (!bSuccess) goto punt; } if ((*pSprite).m_pZNext == nullptr) { // once we have restored all backgrounds if ((m_nZOrder == SPRITE_TOPMOST) && // ... then place us at the head of the (m_nId != (*pZHead).m_nId)) { // ... z order chain if we are to be topmost if ((*pSprite).m_nId == m_nId) // maintain pointer to last in z order pSprite = m_pZPrev; // ... backing up one if it is us pTestSprite = m_pZPrev; // ... since we're moving to the head (*pTestSprite).m_pZNext = m_pZNext; if (m_pZNext != nullptr) (*m_pZNext).m_pZPrev = pTestSprite; m_pZNext = pZHead; m_pZPrev = nullptr; (*pZHead).m_pZPrev = this; pZHead = this; m_nZPosition = m_nZOrder; } break; } pSprite = (*pSprite).m_pZNext; } pTestSprite = pZHead; // update z order relative positions while ((*pTestSprite).m_pZNext != nullptr) { // ... to ensure increasing ordering if ((*pTestSprite).m_nZOrder == (*(*pTestSprite).m_pZNext).m_nZOrder) (*(*pTestSprite).m_pZNext).m_nZPosition = (*pTestSprite).m_nZPosition + 1; pTestSprite = (*pTestSprite).m_pZNext; } } while (true) { // now repaint the images of the sprites if (m_nId == (*pSprite).m_nId) { // ... onto the work area if (myRect == nullptr) // no image, just erasing our sprite bSuccess = true; else { if (!m_bHaveBackdrop) { // but before we paint in the sprite's image if (m_bRetainBackground) { // ... retain what that area looks like as if (m_pBackground == nullptr) { // ... the new saved background if (!CreateBackground(pDC) || !SetupBackground(pDC)) goto punt; } bSuccess = (*m_pBackgroundDC).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, &workDC, (*myRect).left - unionRect.left, (*myRect).top - unionRect.top, SRCCOPY); if (!m_bRetainContexts || m_bDuplicated) ReleaseBackgroundContext(); if (!bSuccess) goto punt; } m_bPaintOverlap = true; } cPoint.x = (*myRect).left - unionRect.left; // set the destination for where our cPoint.y = (*myRect).top - unionRect.top; // ... sprite will be painted goto paint_sprite; } } else { // save what the background looks like if (!m_bHaveBackdrop && (*pSprite).m_bRetainBackground && (*pSprite).m_bPaintOverlap) { bSuccess = (*(*pSprite).m_pBackgroundDC).BitBlt(0, 0, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, &workDC, (*pSprite).m_cPosition.x - unionRect.left, (*pSprite).m_cPosition.y - unionRect.top, SRCCOPY); if (!(*pSprite).m_bRetainContexts || (*pSprite).m_bDuplicated) (*pSprite).ReleaseBackgroundContext(); if (!bSuccess) goto punt; } cPoint.x = (*pSprite).m_cPosition.x - unionRect.left; // set the destination for sprite cPoint.y = (*pSprite).m_cPosition.y - unionRect.top; paint_sprite: if ((*pSprite).m_bPaintOverlap) { bSuccess = (!(*pSprite).m_bMasked || (*pSprite).SetupMask(pDC)) && // setup image and mask contexts (*pSprite).SetupImage(pDC); if (bSuccess) { if ((*pSprite).m_bMasked) { // need to do a masked transfer ... workDC.BitBlt(cPoint.x, // ... mask away where image will go cPoint.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, SRCAND); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY); #endif workDC.BitBlt(cPoint.x, // ... paint image into cleared area cPoint.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pImageDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, SRCPAINT); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY); #endif } else { workDC.BitBlt(cPoint.x, // just paint the image cPoint.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pImageDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, SRCCOPY); #if SPRITE_DEBUG bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY); #endif } if (!(*pSprite).m_bRetainContexts) { // release contexts if not optimizing (*pSprite).ReleaseImageContext(); if ((*pSprite).m_bMasked) (*pSprite).ReleaseMaskContext(); } } } } if (!bSuccess) goto punt; if ((*pSprite).m_pZPrev == nullptr) // see if done with image painting break; pSprite = (*pSprite).m_pZPrev; } (*pDC).BitBlt(baseRect.left, // paint only the part of the work area baseRect.top, // ... spanned by our sprites source baseRect.right - baseRect.left, // ... and destination locations to baseRect.bottom - baseRect.top, // ... the screen; i.e. doing the whole &workDC, // ... work area would be painting stuff baseRect.left - unionRect.left, // ... that wasn't modified baseRect.top - unionRect.top, SRCCOPY); workDC.SelectObject(pBitmapOld); // tear down the work area's bitmap, palette if (m_pPalette != nullptr) { // ... and device context, as well as the workDC.SelectPalette(pPalOldWork, false); // ... destination's palette (*pDC).SelectPalette(pPalOld, false); } workDC.DeleteDC(); (*pBitmap).DeleteObject(); delete pBitmap; return true; } } punt: if (pBitmapOld != nullptr) // we failed, so tear down the resources used workDC.SelectObject(pBitmapOld); // ... map out the bitmap if (pPalOldWork != nullptr) // ... map out the palette workDC.SelectPalette(pPalOldWork, false); if (workDC.m_hDC != nullptr) // ... release the context workDC.DeleteDC(); (*pBitmap).DeleteObject(); // ... delete the bitmap delete pBitmap; if (pPalOld != nullptr) // map out the palette from the output context (*pDC).SelectPalette(pPalOld, false); return false; } /************************************************************************* * * EraseSprites() * * Parameters: * CDC *pDC pointer to device context where the sprites are painted * * Return Value: * bool success/failure condition * * Description: For each sprite in the sprite chain, clear the sprite's * image by repainting its stored background and then clear * the background bitmap. * ************************************************************************/ bool CSprite::EraseSprites(CDC *pDC) { CSprite *pSprite = nullptr; pSprite = m_pSpriteChain; while (pSprite != nullptr) { if (!(*pSprite).EraseSprite(pDC)) return false; pSprite = (*pSprite).m_pNext; } return true; } /************************************************************************* * * EraseSprite() * * Parameters: * CDC *pDC pointer to device context where the sprite is painted * * Return Value: * bool success/failure condition * * Description: Clear the sprite's image by repainting its stored background * and then clear the background bitmap. * ************************************************************************/ bool CSprite::EraseSprite(CDC *pDC) { if (!m_bVisible) // punt if not visible return true; if (RefreshBackground(pDC)) { // repaint the background ClearBackground(); // clear the background art m_bPositioned = false; // no longer has a real position return true; } return false; } /************************************************************************* * * TestInterception() * * Parameters: * CDC *pDC pointer to device context where the sprite gets painted * CSprite * pTestSprite pointer to a lone test sprite for testing * CPoint *pPoint optional address of place to store estimated pixel overlap * * Return Value: * CSprite * pointer to sprite touched, or nullptr * * Description: Determine whether a given sprite touches another given sprite. * The defining rectangular areas of the sprites are tested first, * and bit masking techniques are used if both sprites are masked. * ************************************************************************/ bool CSprite::TestInterception(CDC *pDC, CSprite * pTestSprite, CPoint *pPoint) { CRect myRect, // rectangle occupied by current sprite testRect, // sprite retangle to be tested against overlapRect; // area of overlap between rectangles if (!m_bIntercepts || !(*pTestSprite).m_bIntercepts) // punt if no interception allowed return false; myRect = m_cRect; // acquire the rectangle for base sprite if (m_nId != (*pTestSprite).m_nId) { // be sure to not test against ourself testRect = (*pTestSprite).m_cRect; // get bounding rectangle to test against if (overlapRect.IntersectRect(&myRect, &testRect)) { // use simple rectangle screening first if (!m_bMasked || // ... and if that succeeds, see if we !(*pTestSprite).m_bMasked || // ... and if that succeeds, see if we SpritesOverlap(pDC, pTestSprite, pPoint)) // ... have image masks that overlap return true; // got a simple or complex overlap } } return false; } /************************************************************************* * * Interception() * * Parameters: * CRect *newRect rectangle defining the new location of a sprite * CSprite * pointer to a lone sprite or a sprite chain * * Return Value: * CSprite * pointer to sprite touched, or nullptr * * Description: Determine whether a sprite will touch another sprite if * moved to its new location. Simply determine whether * the defining rectangular areas of the sprites intersect * for basic sprites. * ************************************************************************/ CSprite *CSprite::Interception(CRect *newRect, CSprite * pTestSprite) { CSprite *pSprite; // pointer to current sprite CRect myRect, // rectangle occupied by current sprite testRect, // sprite retangle to be tested against overlapRect; // area of overlap between rectangles if (!m_bIntercepts) // punt if interceptions not allowed return nullptr; pSprite = pTestSprite; // get first sprite to be tested myRect = *newRect; // acquire the rectangle for base sprite while (pSprite != nullptr) { // thumb through the sprite chain if ((m_nId != (*pSprite).m_nId) && // be sure to not test against ourself (*pSprite).m_bIntercepts) { // ... and only test against overlapping sprites testRect = (*pSprite).m_cRect; // sprites touch if their rectangles intersect if (overlapRect.IntersectRect(&myRect, &testRect)) // does our sprite overlap another? return (pSprite); // ... if so return a pointer to it } pSprite = (*pSprite).m_pNext; } // fetch next sprite in chain for testing return nullptr; } /************************************************************************* * * Interception() * * Parameters: * CDC *pDC pointer to device context where the sprite gets painted * CSprite * pTestSprite pointer to a lone test sprite or a sprite chain * * Return Value: * CSprite * pointer to sprite touched, or nullptr * * Description: Determine whether a sprite will touch another sprite if * moved to its new location. Simply determine whether * the defining rectangular areas of the sprites intersect * for basic sprites; use bit masking techniques for the * complex sprite images. * ************************************************************************/ CSprite *CSprite::Interception(CDC *pDC, CSprite * pTestSprite) { CSprite *pSprite = nullptr; // pointer to current sprite pSprite = pTestSprite; // get first sprite to be tested while (pSprite != nullptr) { // thumb through the entire sprite collection if (TestInterception(pDC, pSprite, nullptr)) // ... testing against each sprite in turn return (pSprite); // found an interception pSprite = (*pSprite).m_pNext; } // fetch next sprite in chain for testing return nullptr; } /************************************************************************* * * SpritesOverlap() * * Parameters: * CDC *pDC pointer to device context where the sprite gets painted * CSprite * pSprite pointer to a lone test sprite * CPoint *pPoint optional address of place to store estimated pixel overlap * * Return Value: * bool success/failure condition * * Description: Determine whether a sprite touches another sprite by * checking for overlap in their monochrome bitmap masks * ************************************************************************/ bool CSprite::SpritesOverlap(CDC * pDC, CSprite * pSprite, CPoint *pPoint) { bool bSuccess = false; // success/failure return status CRect unionRect; // rectangle enclosing old and new sprite locations CDC *cDC1 = nullptr, // device context for sprite 1 *cDC2 = nullptr; // device context for sprite 2 CBitmap *pBitmap1 = nullptr, // bitmap for context 1 *pBitmap2 = nullptr, // bitmap for context 2 *pBitmap1Old = nullptr, // bitmap previously mapped to context 1 *pBitmap2Old = nullptr; // bitmap previously mapped to context 2 byte *chPixels = nullptr; // buffer for holding pixel values int dx, dy, // dimensions of context bitmaps i, j; size_t stN; uint32 dwN; //, dwByte; // delta sizes of work area's bitmap BITMAP cBitmapData; int bx, by, bdx, bdy; if (!m_bVisible || !(*pSprite).m_bVisible) // punt if not visible return false; if (pDC == nullptr) // punt if no output context return false; unionRect.UnionRect(m_cRect, (*pSprite).m_cRect); // calculate the smallest enclosing rectangle that dx = unionRect.right - unionRect.left; // ... contains the bitmap area where the sprite was dy = unionRect.bottom - unionRect.top; // ... and the bitmap area where it will be next // Allocate a pixel buffer dwN = stN = dx * dy; chPixels = (byte *) calloc((size_t) 1, stN); if (!chPixels) return false; cDC1 = new CDC(); // get objects for the offscreen bitmaps cDC2 = new CDC(); // ... i.e. for the contexts and bitmaps themselves pBitmap1 = new CBitmap(); pBitmap2 = new CBitmap(); if ((cDC1 != nullptr) && // verify we got the objects we asked for (cDC2 != nullptr) && (*cDC1).CreateCompatibleDC(pDC) && // create the actual device contexts (*cDC2).CreateCompatibleDC(pDC) && (*pBitmap1).CreateBitmap(dx, dy, 1, 1, chPixels) && // create the actual bitmaps (*pBitmap2).CreateBitmap(dx, dy, 1, 1, chPixels)) { pBitmap1Old = (*cDC1).SelectObject(pBitmap1); // map the bitmaps into the contexts pBitmap2Old = (*cDC2).SelectObject(pBitmap2); if ((pBitmap1Old != nullptr) && (pBitmap2Old != nullptr)) { bx = m_cPosition.x - unionRect.left; // get positioning information for sprite 1 by = m_cPosition.y - unionRect.top; bdx = m_cSize.cx; bdy = m_cSize.cy; bSuccess = SetupMask(pDC); if (bSuccess) { (*cDC1).BitBlt(bx, by, bdx, bdy, m_pMaskDC, m_cImageRect.left, m_cImageRect.top, NOTSRCCOPY); // get sprite 1's mask if (!m_bRetainContexts) ReleaseMaskContext(); bx = (*pSprite).m_cPosition.x - unionRect.left; // get positioning information for sprite 2 by = (*pSprite).m_cPosition.y - unionRect.top; bdx = (*pSprite).m_cSize.cx; bdy = (*pSprite).m_cSize.cy; bSuccess = (*pSprite).SetupMask(pDC); if (bSuccess) { (*cDC2).BitBlt(bx, by, bdx, bdy, (*pSprite).m_pMaskDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, NOTSRCCOPY); // get sprite 2's mask if (!(*pSprite).m_bRetainContexts) (*pSprite).ReleaseMaskContext(); (*cDC1).BitBlt(0, 0, dx, dy, cDC2, 0, 0, SRCAND); // logically AND the masks together (*cDC1).SelectObject(pBitmap1Old); // ... leaving bits set where they overlap pBitmap1Old = nullptr; (*pBitmap1).GetBitmapBits(dwN, chPixels); // fetch the image we created (*pBitmap1).GetObject(sizeof(BITMAP), &cBitmapData); // .. get the scanline length bSuccess = false; for (i = 0; i < cBitmapData.bmHeight; i++) { // ... and look for a byte that is nonzero for (j = 0; j < cBitmapData.bmWidthBytes; j++) {// ... in which case we have an image overlap if (chPixels[(i * cBitmapData.bmWidthBytes) + j] != 0) { bSuccess = true; if (pPoint != nullptr) { // estimate point of intersection (*pPoint).x = j - (m_cPosition.x - unionRect.left); (*pPoint).y = i - (m_cPosition.y - unionRect.top); if ((*pPoint).x < 0) (*pPoint).x = 0; if ((*pPoint).y < 0) (*pPoint).y = 0; } break; } } } } } } } free(chPixels); // free up the work area's bitmap if (pBitmap1Old != nullptr) // map out the bitmaps we used (*cDC1).SelectObject(pBitmap1Old); if (pBitmap2Old != nullptr) (*cDC2).SelectObject(pBitmap2Old); if (pBitmap1 != nullptr) { // delete the bitmap resources and objects (*pBitmap1).DeleteObject(); delete pBitmap1; } if (pBitmap2 != nullptr) { (*pBitmap2).DeleteObject(); delete pBitmap2; } if (cDC1 != nullptr) { // release the contexts and delete the objects (*cDC1).DeleteDC(); delete cDC1; } if (cDC2 != nullptr) { (*cDC2).DeleteDC(); delete cDC2; } return bSuccess; // return success/failure } /************************************************************************* * * Touched() * * Parameters: * CPoint testPoint X/Y point against which to test * * Return Value: * bool success/failure condition * * Description: Determine whether a sprite is touched by the given point * ************************************************************************/ bool CSprite::Touching(CPoint myPoint) { if (m_bIntercepts && // ignoring sprites that don't intercept m_cRect.PtInRect(myPoint)) // see if the point is in the sprite's rectangle return true; // ... and if so, return success return false; } /************************************************************************* * * Touched() * * Parameters: * CPoint testPoint X/Y point against which to test succeeding sprites * CSprite * pointer to sprite with which to begin testing * * Return Value: * CSprite * pointer to sprite touched, or nullptr * * Description: Determine whether a sprite is touched by the given point, * if not test against succeeding members in its chain. * ************************************************************************/ CSprite *CSprite::Touched(CPoint myPoint, CSprite *pSprite) { CRect testRect; // sprite area to be tested while (pSprite != nullptr) { // thumb through the entire sprite collection if ((*pSprite).m_bIntercepts) { // ... ignoring sprites that don't intercept testRect = (*pSprite).m_cRect; if (testRect.PtInRect(myPoint)) // See if the point is in the sprite's rectangle return (pSprite); // ... and if so, return a pointer to it } pSprite = (*pSprite).m_pNext; } // fetch next sprite for testing return nullptr; } /************************************************************************* * * SetPosition() * * Parameters: * int x coordinate which identifies where to begin painting * int y the sprite, and which corresponds to the upper lefthand * corner location of the sprite's bitmap * * Return Value: none * * Description: Establish the new location, and encompassing rectangular * area, for the sprite. * ************************************************************************/ void CSprite::SetPosition(int x, int y) { m_bPositioned = true; // now have a real location m_cPosition.x = x; // establish the new location of the sprite m_cPosition.y = y; // ... and setup the bitmap's bounding rectangle m_cRect.SetRect(m_cPosition.x, m_cPosition.y, m_cPosition.x + m_cSize.cx, m_cPosition.y + m_cSize.cy); } /************************************************************************* * * CropImage() * * Parameters: * CDC *pDC pointer to device context used by the sprite * CRect *pRect pointer of rectangular area to be cropped; * position is relative to upper left hand corner * of sprite image which is the origin (0,0) * * bool success/failure condition * * Description: Crop a rectangular area from a sprite's image; * i.e. turn it black so that the background art * shows through, and make the corresponding area * in the mask be white. * ************************************************************************/ bool CSprite::CropImage(CDC *pDC, CRect *pRect) { CRect myRect; CBrush myBrush; bool bSuccess = false; myRect = *pRect; // offset crop area by image rect myRect.left += m_cImageRect.left; myRect.right += m_cImageRect.left; if (m_pImage != nullptr) { if (SetupImage(pDC)) { // blacken image section to crop it myBrush.CreateStockObject(BLACK_BRUSH); (*m_pImageDC).FillRect(&myRect, &myBrush); if (!m_bMasked || (m_pMask == nullptr)) bSuccess = true; else { bSuccess = SetupMask(pDC); // whiten mask section to crop it myBrush.CreateStockObject(WHITE_BRUSH); (*m_pMaskDC).FillRect(&myRect, &myBrush); } if (bSuccess && m_bHaveBackdrop) { if (GetBackdropDC(pDC) == nullptr) bSuccess = false; else { bSuccess = (*pDC).BitBlt( m_cRect.left + (*pRect).left, m_cRect.top + (*pRect).top, (*pRect).right - (*pRect).left, (*pRect).bottom - (*pRect).top, m_pBackdropDC, m_cRect.left + (*pRect).left, m_cRect.top + (*pRect).top, SRCCOPY); ReleaseBackdropDC(); } } else if (bSuccess && (m_pBackground != nullptr)) { bSuccess = SetupBackground(pDC); // update background with cropped area if (bSuccess) { bSuccess = (*pDC).BitBlt( m_cRect.left + (*pRect).left, m_cRect.top + (*pRect).top, (*pRect).right - (*pRect).left, (*pRect).bottom - (*pRect).top, m_pBackgroundDC, (*pRect).left, (*pRect).top, SRCCOPY); ReleaseBackgroundContext(); } } } } if (!m_bRetainContexts) { ReleaseImageContext(); ReleaseMaskContext(); } return bSuccess; } /************************************************************************* * * CreateMask() * * Parameters: * CDC *myDC pointer to device context used by the sprite * * Return Value: * bool success/failure condition * * Description: Create a monochrome bitmap mask for the sprite, with * white (1s) for the image, and black (0s) for the background. * ************************************************************************/ bool CSprite::CreateMask(CDC *pDC) { bool bHaveImageContext = false, // keep track of whether contexts exist bHaveMaskContext = false; CSize mySize; if (!m_bVisible) // punt if not visible return false; if (m_pMask != nullptr) // done if already have a mask return true; if (!m_bMasked || // fail if not masked or, no image from (m_pImage == nullptr) || // ... which to obtain a mask, or no (pDC == nullptr)) // ... output context return false; if (m_pImageDC != nullptr) // see if contexts already exist bHaveImageContext = true; if (m_pMaskDC != nullptr) bHaveMaskContext = true; mySize.cx = m_nCelCount ? m_cSize.cx * m_nCelCount : m_cSize.cx; mySize.cy = m_cSize.cy; m_pMask = new CBitmap(); // create object to hold the mask if ((m_pMask != nullptr) && // verify that worked SetupImage(pDC) && // setup the image bitmap and context CreateMaskContext(pDC)) { // create a context for the mask if ((*m_pMask).CreateBitmap(mySize.cx, mySize.cy, 1, 1, nullptr)) { // create mask bitmap m_pMaskOld = (*m_pMaskDC).SelectObject(m_pMask); // map bitmap into context if (m_pMaskOld) { (*m_pMaskDC).BitBlt(0, // copy in the image, doing an inversion 0, // ... so that we can mask out the sprite's mySize.cx, // ... transparent area mySize.cy, m_pImageDC, 0, 0, NOTSRCCOPY); (*m_pImageDC).BitBlt(0, // remove transparent area from sprite image 0, mySize.cx, mySize.cy, m_pMaskDC, 0, 0, SRCAND); (*m_pMaskDC).BitBlt(0, // invert mask again so it can be used later 0, // ... to mask away the sprite image area mySize.cx, // ... from the background for each new mySize.cy, // ... destination m_pMaskDC, 0, 0, DSTINVERT); if (!bHaveImageContext) // release contexts if not optimizing ReleaseImageContext(); if (!bHaveMaskContext) ReleaseMaskContext(); return true; } } } if (!bHaveImageContext) // release contexts if they didn't ReleaseImageContext(); // ... already exist if (!bHaveMaskContext) ReleaseMaskContext(); if (m_pMask != nullptr) { // delete mask bitmap since we failed delete m_pMask; m_pMask = nullptr; } return false; } /************************************************************************* * * CreateBackground() * * Parameters: * CDC *myDC pointer to device context used by the sprite * * Return Value: * bool success/failure condition * * Description: Create an offscreen bitmap to hold the background image * that the sprite covers up. * ************************************************************************/ bool CSprite::CreateBackground(CDC *pDC) { if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop return false; if (m_pBackground != nullptr) // done if already have a background return true; if (pDC != nullptr) { // create an object to hold things m_pBackground = new CBitmap(); if (m_pBackground != nullptr) { // create the background bitmap if ((*m_pBackground).CreateCompatibleBitmap(pDC, m_cSize.cx, m_cSize.cy)) return true; delete m_pBackground; // tear things down if we failed m_pBackground = nullptr; } } return false; } /************************************************************************* * * SetupImage() * * Parameters: * CDC *myDC pointer to device context used by the sprite * * Return Value: * bool success/failure condition * * Description: Establish a device context to hold the sprite's image * and then map in its palette and bitmap. * ************************************************************************/ bool CSprite::SetupImage(CDC * pDC) { bool bHaveContext = false; // whether we already have a context if (!m_bVisible) // punt if not visible return false; if ((m_pImage != nullptr) && // punt if no image bitmap (pDC != nullptr)) { // ... or no output context if (m_pImageDC != nullptr) // see if already have a context bHaveContext = true; if (CreateImageContext(pDC)) { // create a context for the image if (m_pImageOld == nullptr) // ... then map in the bitmap m_pImageOld = (*m_pImageDC).SelectObject(m_pImage); if (m_pImageOld != nullptr) return true; } if (!bHaveContext) // release context if we didn't ReleaseImageContext(); // ... have it already } return false; } /************************************************************************* * * SetupBackground() * * Parameters: * CDC *myDC pointer to device context used by the sprite * * Return Value: * bool success/failure condition * * Description: Establish a device context to hold the sprite's Background * and then map in its palette and bitmap. * ************************************************************************/ bool CSprite::SetupBackground(CDC * pDC) { bool bHaveContext = false; // whether there is already a context if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop return false; if ((m_pBackground != nullptr) && // verify there is a background bitmap (pDC != nullptr)) { // ... and an output context if (m_pBackgroundDC != nullptr) // see if a context already exists bHaveContext = true; if (CreateBackgroundContext(pDC)) { // create a context for if (m_pBackgroundOld == nullptr) // ... and map in the bitmap m_pBackgroundOld = (*m_pBackgroundDC).SelectObject(m_pBackground); if (m_pBackgroundOld != nullptr) return true; } if (!bHaveContext) // release context if we didn't ReleaseBackgroundContext(); // ... have it already } return false; } /************************************************************************* * * SetupMask() * * Parameters: * CDC *myDC pointer to device context used by the sprite * * Return Value: * bool success/failure condition * * Description: Establish a device context to hold the sprite's Mask * and then map in its bitmap. * ************************************************************************/ bool CSprite::SetupMask(CDC * pDC) { bool bHaveContext = false; // whether we have a context if (!m_bVisible) // punt if not visible return false; if (m_bMasked && // verify we have a bitmap (pDC != nullptr)) { // ... and an output context if (m_pMaskDC != nullptr) // see if we already have a context bHaveContext = true; if (CreateMaskContext(pDC) && // create a context for the mask CreateMask(pDC)) { // create the mask bitmap and content if (m_pMaskOld == nullptr) // map bitmap into context m_pMaskOld = (*m_pMaskDC).SelectObject(m_pMask); if (m_pMaskOld != nullptr) return true; } if (!bHaveContext) // release context if we didn't ReleaseMaskContext(); // ... already have it } return false; } /************************************************************************* * * CreateImageContext() * * Parameters: * CDC *myDC pointer to device context used by the sprite * * Return Value: * bool success/failure condition * * Description: Create a device context to hold the sprite's image. * ************************************************************************/ bool CSprite::CreateImageContext(CDC * pDC) { if (!m_bVisible) // punt if not visible return false; if (pDC != nullptr) { // verify output context if (m_pImageDC == nullptr) { // if we don't already have m_pImageDC = new CDC(); // ... a context for the image if (!(*m_pImageDC).CreateCompatibleDC(pDC)) // ... we create one now goto Error; } if ((m_pPalette != nullptr) && (m_pPalImageOld == nullptr)) { // map in palette if present m_pPalImageOld = (*m_pImageDC).SelectPalette(m_pPalette, false); (*m_pImageDC).RealizePalette(); } return true; } Error: ReleaseImageContext(); // failed, so release the context return false; } /************************************************************************* * * CreateBackgroundContext() * * Parameters: * CDC *myDC pointer to device context used by the sprite * * Return Value: * bool success/failure condition * * Description: Create a device context to hold the sprite's background. * ************************************************************************/ bool CSprite::CreateBackgroundContext(CDC * pDC) { if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop return false; if (pDC != nullptr) { // verify the output context if (m_pBackgroundDC == nullptr) { // if we don't have a context m_pBackgroundDC = new CDC(); // ... then we create one now if (!(*m_pBackgroundDC).CreateCompatibleDC(pDC)) goto Error; } if ((m_pPalette != nullptr) && (m_pPalBackOld == nullptr)) { // map in palette if present m_pPalBackOld = (*m_pBackgroundDC).SelectPalette(m_pPalette, false); (*m_pBackgroundDC).RealizePalette(); } return true; } Error: ReleaseBackgroundContext(); // failed, so release the context return false; } /************************************************************************* * * CreateMaskContext() * * Parameters: * CDC *myDC pointer to device context used by the sprite * * Return Value: * bool success/failure condition * * Description: Create a device context to hold the sprite's mask. * ************************************************************************/ bool CSprite::CreateMaskContext(CDC * pDC) { if (!m_bVisible) // punt if not visible return false; if (m_bMasked && // verify it is a masked sprite (pDC != nullptr)) { // ... and we have an output context if (m_pMaskDC != nullptr) // done if already have a context return true; m_pMaskDC = new CDC(); // create an object for the context if ((m_pMaskDC != nullptr) && // ... then create the context itself (*m_pMaskDC).CreateCompatibleDC(pDC)) return true; ReleaseMaskContext(); } // release the context if we failed return false; } /************************************************************************* * * ReleaseImageContext() * * Parameters: none * * Return Value: none * * Description: Release the sprite's image context. * ************************************************************************/ void CSprite::ReleaseImageContext() { if (!m_bVisible) // punt if not visible return; if (m_pImageDC != nullptr) { // if there is an image device context if (m_pImageOld != nullptr) // ... map out existing bitmap (*m_pImageDC).SelectObject(m_pImageOld); if (m_pPalImageOld != nullptr) // ... map out the palette (*m_pImageDC).SelectPalette(m_pPalImageOld, false); (*m_pImageDC).DeleteDC(); // ... release the context delete m_pImageDC; // ... then delete the device context m_pImageOld = nullptr; m_pPalImageOld = nullptr; m_pImageDC = nullptr; } } /************************************************************************* * * ReleaseBackgroundContext() * * Parameters: none * * Return Value: none * * Description: Release the sprite's background context. * ************************************************************************/ void CSprite::ReleaseBackgroundContext() { if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop return; if (m_pBackgroundDC != nullptr) { // if there is a background device context if (m_pBackgroundOld != nullptr) { // ... map out existing bitmap (*m_pBackgroundDC).SelectObject(m_pBackgroundOld); m_pBackgroundOld = nullptr; } if (m_pPalBackOld != nullptr) { // ... map out the palette (*m_pBackgroundDC).SelectPalette(m_pPalBackOld, false); m_pPalBackOld = nullptr; } (*m_pBackgroundDC).DeleteDC(); // ... release the context delete m_pBackgroundDC; m_pBackgroundDC = nullptr; } // ... then delete the device context } /************************************************************************* * * ReleaseMaskContext() * * Parameters: none * * Return Value: none * * Description: Release the sprite's mask context. * ************************************************************************/ void CSprite::ReleaseMaskContext() { if (!m_bVisible) // punt if not visible return; if (m_bMasked && (m_pMaskDC != nullptr)) { // if there is a mask device context if (m_pMaskOld != nullptr) // ... map out existing bitmap (*m_pMaskDC).SelectObject(m_pMaskOld); (*m_pMaskDC).DeleteDC(); // ... release the context delete m_pMaskDC; // ... then delete the device context m_pMaskOld = nullptr; m_pMaskDC = nullptr; } } /************************************************************************* * * ClearImage() * * Parameters: none * * Return Value: none * * Description: Destroy the sprite's image bitmap and context * ************************************************************************/ void CSprite::ClearImage() { if (!m_bVisible) // punt if not visible return; if (!m_bDuplicated) { ReleaseImageContext(); // release the imnage context if (m_pImage != nullptr) { // destroy the image bitmap object (*m_pImage).DeleteObject(); // ... if present delete m_pImage; } } m_pImage = nullptr; } /************************************************************************* * * ClearBackgrounds() * * Parameters: * CDC *pDC pointer to device context where the sprites are painted * * Return Value: * bool success/failure condition * * Description: For each sprite in the sprite chain, clear the sprite's * background bitmap. * ************************************************************************/ void CSprite::ClearBackgrounds() { CSprite *pSprite = nullptr; pSprite = m_pSpriteChain; while (pSprite != nullptr) { (*pSprite).ClearBackground(); pSprite = (*pSprite).m_pNext; } } /************************************************************************* * * ClearBackground() * * Parameters: none * * Return Value: none * * Description: Destroy the sprite's background bitmap and context * ************************************************************************/ void CSprite::ClearBackground() { m_nZPosition = m_nZOrder; // reset z ordering m_bOverlaps = false; // no longer overlaps other sprites m_bPositioned = false; // no longer has a real position if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop return; ReleaseBackgroundContext(); // release the background context if (m_pBackground != nullptr) { // destroy the backgrond bitmap object (*m_pBackground).DeleteObject(); // ... if present delete m_pBackground; m_pBackground = nullptr; } } /************************************************************************* * * SetRetainBackground() * * Parameters: true / false * * Return Value: none * * Description: set for retaining or ignoring background images for updates, * and clear away existing background image if required. * ************************************************************************/ void CSprite::SetRetainBackground(bool bValue) { if (!bValue) // if not retaining backgrounds now ClearBackground(); // ... then clear what we have m_bRetainBackground = bValue; // set new state } /************************************************************************* * * ClearMask() * * Parameters: none * * Return Value: none * * Description: Destroy the sprite's mask bitmap and context * ************************************************************************/ void CSprite::ClearMask() { if (!m_bVisible) // punt if not visible return; if (m_bMasked && !m_bDuplicated) { ReleaseMaskContext(); // release mask context if (m_pMask != nullptr) { // destroy the Mask bitmap resource and object (*m_pMask).DeleteObject(); // ... if present delete m_pMask; } } m_pMask = nullptr; } /************************************************************************* * * ClearPalette() * * Parameters: none * * Return Value: none * * Description: Destroy the sprite's palette resource if it is not * shared by other sprites. * ************************************************************************/ void CSprite::ClearPalette() { if ((m_pPalette != nullptr) && !m_bSharedPalette) { // explicitly delete the palette resource (*m_pPalette).DeleteObject(); // ... if it is not shared by other sprites delete m_pPalette; } m_bSharedPalette = false; m_pPalette = nullptr; } /************************************************************************* * * GetBackdropDC() * * Parameters: none * * Return Value: * CDC * pointer to device context or nullptr * * Description: setup the device context for the backdrop. * ************************************************************************/ CDC *CSprite::GetBackdropDC(CDC *pDC) { if (m_pBackdropDC != nullptr) // punt if no context return (m_pBackdropDC); m_pBackdropDC = new CDC(); if (!(*m_pBackdropDC).CreateCompatibleDC(pDC)) { delete m_pBackdropDC; m_pBackdropDC = nullptr; return nullptr; } if (m_pBackdropPalette != nullptr) { // map in the palette if present m_pBackdropPalOld = (*m_pBackdropDC).SelectPalette(m_pBackdropPalette, false); if (m_pBackdropPalOld == nullptr) { // punt if not successful delete m_pBackdropDC; m_pBackdropDC = nullptr; return nullptr; } (*m_pBackdropDC).RealizePalette(); // make the system use the palette } m_pBackdropOld = (*m_pBackdropDC).SelectObject(m_pBackdrop); // map in our bitmap if (m_pBackdropOld == nullptr) { if (m_pBackdropPalOld != nullptr) { // map out palette on failure (*m_pBackdropDC).SelectPalette(m_pBackdropPalOld, false); m_pBackdropPalOld = nullptr; delete m_pBackdropDC; m_pBackdropDC = nullptr; } return nullptr; } return (m_pBackdropDC); // send back the context } /************************************************************************* * * ReleaseBackdropDC() * * Parameters: none * * Return Value: none * * Description: release the backdrop device context resources. * ************************************************************************/ void CSprite::ReleaseBackdropDC() { if (m_pBackdropOld != nullptr) { // map back the previous bitmap (*m_pBackdropDC).SelectObject(m_pBackdropOld); m_pBackdropOld = nullptr; } if (m_pBackdropPalOld != nullptr) { // map back the previous palette (*m_pBackdropDC).SelectPalette(m_pBackdropPalOld, false); m_pBackdropPalOld = nullptr; } if (m_pBackdropDC != nullptr) { delete m_pBackdropDC; m_pBackdropDC = nullptr; } } } // namespace HodjNPodj } // namespace Bagel