From d8944244a1ea9610544a5cfb58b8b7cda946f132 Mon Sep 17 00:00:00 2001 From: Quote58 Date: Tue, 13 Sep 2022 03:19:29 -0400 Subject: [PATCH] IMMORTAL: Implement sprite drawing through superSprites() --- engines/immortal/immortal.h | 50 ++++++++--------- engines/immortal/sprites.cpp | 104 ++++++++++++++++++++--------------- 2 files changed, 84 insertions(+), 70 deletions(-) diff --git a/engines/immortal/immortal.h b/engines/immortal/immortal.h index 02b2d5e45de..fe20b7cfa18 100644 --- a/engines/immortal/immortal.h +++ b/engines/immortal/immortal.h @@ -189,21 +189,21 @@ public: const int kScreenH__ = 128; // ??? const int kViewPortW = 256; const int kViewPortH = 128; - const int kScreenSize = (kResH * kResV) * 2; // The size of the screen buffer is 320x200 - const int kScreenLeft = 32; - const int kScreenTop = 20; + const int kScreenSize = (kResH * kResV) * 2; // The size of the screen buffer is (320x200) * 2 byte words + const uint16 kScreenLeft = 32; + const uint16 kScreenTop = 20; const int kTextLeft = 8; const int kTextTop = 4; const int kGaugeX = 0; const int kGaugeY = -13; // ??? - const int kScreenBMW = 160; // Screen BitMap Width? + const uint16 kScreenBMW = 160; // Screen BitMap Width? const uint16 kChrW = 64; const uint16 kChrH = 32; const uint16 kChrH2 = kChrH * 2; const uint16 kChrH3 = kChrH * 3; - const int kChrLen = (kChrW / 2) * kChrH; - const int kChrBMW = kChrW / 2; - const int kLCutaway = 4; + const uint16 kChrLen = (kChrW / 2) * kChrH; + const uint16 kChrBMW = kChrW / 2; + const uint16 kLCutaway = 4; const uint16 kChrDy[19] = {kChr0, kChrH, kChrH2, kChrH, kChrH2, kChrH2, kChrH, kChrH2, kChrH2, kChr0, @@ -227,22 +227,22 @@ public: const int kPaletteOffset = 21205; // This is the byte position of the palette data in the disk // Sprite constants - const int kMaxSpriteW = 64; - const int kMaxSpriteH = 64; - const int kSpriteDY = 32; - const int kVSX = kMaxSpriteW; - const int kVSY = kSpriteDY; + const uint16 kMaxSpriteW = 64; + const uint16 kMaxSpriteH = 64; + const uint16 kSpriteDY = 32; + const uint16 kVSX = kMaxSpriteW; + const uint16 kVSY = kSpriteDY; const uint16 kVSBMW = (kViewPortW + kMaxSpriteW) / 2; - const int kVSLen = kVSBMW * (kViewPortH + kMaxSpriteH); - const int kVSDY = 32; // difference from top of screen to top of viewport in the virtual screen buffer - const int kMySuperBottom = kVSDY + kViewPortH; - const int kSuperBottom = 200; - const int kMySuperTop = kVSDY; - const int kSuperTop = 0; - const int kViewPortSpX = 32; - const int kViewPortSpY = 0; - const int kWizardX = 28; // Common sprite center for some reason - const int kWizardY = 37; + const uint16 kVSLen = kVSBMW * (kViewPortH + kMaxSpriteH); + const uint16 kVSDY = 32; // difference from top of screen to top of viewport in the virtual screen buffer + const uint16 kMySuperBottom = kVSDY + kViewPortH; + const uint16 kSuperBottom = 200; + const uint16 kMySuperTop = kVSDY; + const uint16 kSuperTop = 0; + const uint16 kViewPortSpX = 32; + const uint16 kViewPortSpY = 0; + const uint16 kWizardX = 28; // Common sprite center for some reason + const uint16 kWizardY = 37; // Asset constants const char kGaugeOn = 1; // On uses the sprite at index 1 of the font spriteset @@ -409,6 +409,7 @@ GenericSprite _genSprites[6]; void addSprites(); // Add all active sprites that are in the viewport, into a list that will be sorted by priority void sortDrawItems(); // Sort said items void drawItems(); // Draw the items over the background + void setPen(uint16 penX, uint16 penY); // Sets the 'pen' x and y positions, including making y negative if above a certain point // Music void toggleSound(); // Actually pauses the sound, doesn't just turn it off/mute @@ -584,10 +585,9 @@ GenericSprite _genSprites[6]; void initDataSprite(Common::SeekableReadStream *f, DataSprite *d, int index, uint16 cenX, uint16 cenY); // Initializes the data sprite // Main - void superSprite(DataSprite *dSprite, uint16 x, uint16 y, int img, uint16 bmw, byte *dst, int superTop, int superBottom); - bool clipSprite(uint16 &height, uint16 &pointIndex, uint16 &skipY, DataSprite *dSprite, uint16 &pointX, uint16 &pointY, int img, uint16 bmw, int superTop, int superBottom); + void superSprite(DataSprite *dSprite, uint16 x, uint16 y, int img, uint16 bmw, byte *dst, uint16 superTop, uint16 superBottom); + bool clipSprite(uint16 &height, uint16 &pointIndex, uint16 &skipY, DataSprite *dSprite, uint16 &pointX, uint16 &pointY, int img, uint16 bmw, uint16 superTop, uint16 superBottom); void spriteAligned(DataSprite *dSprite, Image &img, uint16 &skipY, uint16 &pointIndex, uint16 &height, uint16 bmw, byte *dst); - void spriteNotAligned(); /* * [Compression.cpp] Functions from Compression.GS diff --git a/engines/immortal/sprites.cpp b/engines/immortal/sprites.cpp index 45e442eb3f4..3a982a91699 100644 --- a/engines/immortal/sprites.cpp +++ b/engines/immortal/sprites.cpp @@ -76,7 +76,7 @@ void ImmortalEngine::initDataSprite(Common::SeekableReadStream *f, DataSprite *d uint16 numImages = f->readUint16LE(); d->_numImages = numImages; - //debug("Number of Frames: %d", numFrames); + //debug("Number of Frames: %d", numImages); // Only here for dragon, but just in case, it's a high number so it should catch others if (numImages >= 0x0200) { @@ -118,92 +118,110 @@ void ImmortalEngine::initDataSprite(Common::SeekableReadStream *f, DataSprite *d d->_images = images; } -bool ImmortalEngine::clipSprite(uint16 &height, uint16 &pointIndex, uint16 &skipY, DataSprite *dSprite, uint16 &pointX, uint16 &pointY, int img, uint16 bmw, int superTop, int superBottom) { +bool ImmortalEngine::clipSprite(uint16 &height, uint16 &pointIndex, uint16 &skipY, DataSprite *dSprite, uint16 &pointX, uint16 &pointY, int img, uint16 bmw, uint16 superTop, uint16 superBottom) { + /* Something important to note here: + * In the source, bmw is not *2, and pointX is /2. However, the source + * was using a buffer of 2 pixels per byte. In ScummVM, the screen buffer + * is 1 pixel per byte. This means some calculations are slightly different. + */ + // This bit is to get the base index into the screen buffer, unless that's already been done, which is _lastPoint if ((pointY != _lastY) || (bmw != _lastBMW)) { _lastBMW = bmw; _lastY = pointY; - if (pointY < 0x80) { - _lastPoint = pointY * bmw; + if (pointY < kMaskNeg) { + // The source does not double the bmw here to get the bytes, why not? + _lastPoint = pointY * (bmw * 2); } else { - pointY = (pointY ^ 0xFF) + 1; - _lastPoint = pointY * bmw; + // Screen wrapping?? + uint16 temp = (0 - pointY) + 1; + _lastPoint = temp * bmw; _lastPoint = 0 - _lastPoint; } } pointIndex = _lastPoint; - return false; - // Now we begin clipping, starting with totally offscreen - // We do this by checking if the sprite is above the top of the screen, or below the bottom of it if (pointY > superBottom) { return true; - } else if ((height + pointY) < superTop) { + } else if ((pointY + height) < superTop) { return true; - // Now we actually clip top/bottom parts + /* The actual clipping is pretty simple: + * Lower height = stop drawing the sprite early. Higher SkipY = start drawing the sprite late + * So we just determine the delta for each based on superTop and superBottom + */ } else { // Starting with checking if any of the sprite is under the bottom of the screen - if ((height + pointY) >= superBottom) { + if ((pointY + height) >= superBottom) { height = superBottom - pointY; } // Next we get the difference of overlap from the sprite if it is above the top - if ((superTop - pointY) < 0x8000) { + if (uint16((superTop - pointY)) < kMaskNeg) { skipY = (superTop - pointY); } // The image is clipped, time to move the index to the sprite's first scanline base position - pointIndex += (pointX / 2) + dSprite->_images[img]._rectW; + pointIndex += (pointX) + dSprite->_images[img]._rectW; } return false; } void ImmortalEngine::spriteAligned(DataSprite *dSprite, Image &img, uint16 &skipY, uint16 &pointIndex, uint16 &height, uint16 bmw, byte *dst) { - //debug("draw the sprite"); + /* This is an approximation of the sprite drawing system in the source. + * It is an approximation because the source needed to do some things + * that aren't relevant anymore, and it had some....creative solutions. + * For example, transparency was handled with a 256 byte table of masks + * that was indexed by the pixel itself, and used to find what nyble needed + * to be masked. However we are using a slightly different kind of screen buffer, + * and so I chose a more traditional method. Likewise, alignement was + * relevant for the source, but is not relevant here (thankfully, considering + * how confusing sprite drawing is when not an even position). + */ + byte pixel1 = 0; + byte pixel2 = 0; - debug("%d, %d, %04X", height, skipY, pointIndex); - - byte pixel; - // For debug currently, align to the word by default - pointIndex &= 0xFFFE; - - // Position is weird - pointIndex += 50; - - debug("SPRITE START ------"); + // For every scanline before height for (int y = 0; y < height; y++, pointIndex += (bmw * 2)) { - //debug("%04X, %04X ", pointIndex, img._deltaPos[y]); - - if (img._deltaPos[y] < 0x8000) { + // We increase the position by one screen width + if (img._deltaPos[y] < kMaskNeg) { pointIndex += (img._deltaPos[y] * 2); } + + // And if the delta X for the line is positive, we add it. If negative we subtract else { pointIndex -= ((0 - img._deltaPos[y]) * 2); } + // For every pixel in the scanline for (int x = 0; x < img._scanWidth[y]; x++, pointIndex += 2) { + // SkipY defines the lines we don't draw because they are clipped + if (y >= skipY) { - //if (y > skipY) { - pixel = img._bitmap[y][x]; - _screenBuff[pointIndex] = (pixel & kMask8High) >> 4; - _screenBuff[pointIndex + 1] = pixel & kMask8Low; - //} + // For handling transparency, I chose to simply check if the pixel is 0, + // as that is the transparent colour + pixel1 = (img._bitmap[y][x] & kMask8High) >> 4; + pixel2 = (img._bitmap[y][x] & kMask8Low); + + if (pixel1 != 0) { + _screenBuff[pointIndex] = pixel1; + } + + if (pixel2 != 0) { + _screenBuff[pointIndex + 1] = pixel2; + } + + } } } - debug("SPRITE END -------"); } -void ImmortalEngine::spriteNotAligned() { - -} - -void ImmortalEngine::superSprite(DataSprite *dSprite, uint16 pointX, uint16 pointY, int img, uint16 bmw, byte *dst, int superTop, int superBottom) { +void ImmortalEngine::superSprite(DataSprite *dSprite, uint16 pointX, uint16 pointY, int img, uint16 bmw, byte *dst, uint16 superTop, uint16 superBottom) { // Main image construction routine uint16 cenX = dSprite->_cenX; @@ -220,13 +238,9 @@ void ImmortalEngine::superSprite(DataSprite *dSprite, uint16 pointX, uint16 poin // Normally I would just make the return from clip be reversed, but the idea is that the return would be 'offscreen == true' if (!(clipSprite(height, pointIndex, skipY, dSprite, pointX, pointY, img, bmw, superTop, superBottom))) { - // Alignment is determined by whether the x position of the point is positive or negative - if (pointX >= 0x8000) { - spriteAligned(dSprite, dSprite->_images[img], skipY, pointIndex, height, bmw, dst); - } else { - spriteAligned(dSprite, dSprite->_images[img], skipY, pointIndex, height, bmw, dst); - } + // Alignment was a factor in the assembly because it was essentially 2 pixels per byte. However ScummVM is 1 pixel per byte + spriteAligned(dSprite, dSprite->_images[img], skipY, pointIndex, height, bmw, dst); } }