mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
247 lines
8.6 KiB
C++
247 lines
8.6 KiB
C++
/* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "immortal/immortal.h"
|
|
|
|
/* -- How does image construction work --
|
|
* One thing to note about this translation, is that the source
|
|
* has a lot of address related stuff mixed in to it. This is
|
|
* because 'Super Sprites' could use a screen buffer and sprite
|
|
* data from anywhere in memory, including locations that cross
|
|
* bank boundaries. This means that you don't just have
|
|
* position -> relative position -> screen, you have
|
|
* position -> relative position -> relative *address* position -> screen
|
|
* With that out of the way, here's what a sprite is:
|
|
* A 'Super Sprite' is several layers of structures combined.
|
|
* This is both more and less complex in the source. It is structurally
|
|
* less complicated, only being seen as a sprite + frame, and a cycle.
|
|
* But in reality that comes with complicated indexing and routines
|
|
* designed just to get relative indexes that are already in memory.
|
|
* Instead, I have chosen to clean up the structure a little bit,
|
|
* which in turns makes it slightly more complicated on a top level.
|
|
* What we end up with, basically looks like this:
|
|
* Cycle (ram/rom)
|
|
* |
|
|
* Sprite (ram)
|
|
* |
|
|
* DataSprite (rom)
|
|
* |
|
|
* Frame (rom)
|
|
* |
|
|
* Scanline (rom)
|
|
* |
|
|
* Bitmap (rom)
|
|
*/
|
|
|
|
namespace Immortal {
|
|
|
|
/*
|
|
*
|
|
* ----- -----
|
|
* ----- Main Functions -----
|
|
* ----- -----
|
|
*
|
|
*/
|
|
|
|
// This function is basically setSpriteCenter + getSpriteInfo from the source
|
|
void ImmortalEngine::initDataSprite(Common::SeekableReadStream *f, DataSprite *d, int index, uint16 cenX, uint16 cenY) {
|
|
// We set the center X and Y
|
|
d->_cenX = cenX;
|
|
d->_cenY = cenY;
|
|
|
|
// But now we need to get the rest of the meta data for each frame
|
|
// index is the index of the sprite within the file (not the same as the sprite name enum)
|
|
index *= 8;
|
|
f->seek(index);
|
|
|
|
index = f->readUint16LE();
|
|
uint16 numImages = f->readUint16LE();
|
|
|
|
d->_numImages = numImages;
|
|
|
|
// Only here for dragon, but just in case, it's a high number so it should catch others
|
|
if (numImages >= 0x0200) {
|
|
//debug("** Crazy large value, this isn't a frame number **");
|
|
return;
|
|
}
|
|
|
|
Common::Array<Image> images;
|
|
|
|
for (int i = 0; i < numImages; i++) {
|
|
Image newImage;
|
|
f->seek(index + (i * 2));
|
|
int ptrFrame = f->readUint16LE();
|
|
f->seek(ptrFrame);
|
|
newImage._deltaX = f->readUint16LE() << 1; // This member does not seem to be used in the actual game, and it is not clear whether it needs the << 1 or if that was fixed before release
|
|
newImage._deltaY = f->readUint16LE();
|
|
newImage._rectW = f->readUint16LE();
|
|
newImage._rectH = f->readUint16LE();
|
|
uint16 next = 0;
|
|
for (int j = 0; j < newImage._rectH; j++) {
|
|
next = f->readUint16LE();
|
|
newImage._deltaPos.push_back(next);
|
|
next = f->readUint16LE();
|
|
newImage._scanWidth.push_back(next);
|
|
Common::Array<byte> b;
|
|
b.resize(newImage._scanWidth[j]);
|
|
for (int k = 0; k < newImage._scanWidth[j]; k++) {
|
|
b[k] = f->readByte();
|
|
}
|
|
newImage._bitmap.push_back(b);
|
|
}
|
|
images.push_back(newImage);
|
|
}
|
|
|
|
d->_images = images;
|
|
}
|
|
|
|
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 < kMaskNeg) {
|
|
// For the Apple IIGS, pointY in pixels needed to be converted to bytes. For us, it's the other way around, we need bmw in pixels
|
|
// This should probably use mult16() instead of *
|
|
_lastPoint = pointY * (bmw);
|
|
} else {
|
|
// Screen wrapping?
|
|
uint16 temp = (0 - pointY) + 1;
|
|
_lastPoint = temp * bmw;
|
|
_lastPoint = 0 - _lastPoint;
|
|
}
|
|
}
|
|
|
|
pointIndex = _lastPoint;
|
|
|
|
// Now we begin clipping, starting with totally offscreen
|
|
if (pointY > superBottom) {
|
|
return true;
|
|
|
|
} else if ((pointY + height) < superTop) {
|
|
return true;
|
|
|
|
/* 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 ((pointY + height) >= superBottom) {
|
|
height = superBottom - pointY;
|
|
}
|
|
|
|
// Next we get the difference of overlap from the sprite if it is above the top
|
|
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;// + dSprite->_images[img]._rectW;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ImmortalEngine::spriteAligned(DataSprite *dSprite, Image &img, uint16 &skipY, uint16 &pointIndex, uint16 &height, uint16 bmw, byte *dst) {
|
|
/* 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, and the sprite drawing
|
|
* is not accomplished by indexed a set of code blocks.
|
|
*/
|
|
byte pixel1 = 0;
|
|
byte pixel2 = 0;
|
|
|
|
// For every scanline before height
|
|
for (int y = 0; y < height; y++, pointIndex += (bmw)) {
|
|
|
|
// 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) {
|
|
|
|
// 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;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImmortalEngine::superSprite(DataSprite *dSprite, uint16 pointX, uint16 pointY, int img, uint16 bmw, byte *dst, uint16 superTop, uint16 superBottom) {
|
|
// Main sprite image construction routine
|
|
|
|
// For the Apple IIGS, the bmw is in bytes, but for us it needs to be the reverse, in pixels
|
|
bmw <<= 1;
|
|
|
|
uint16 cenX = dSprite->_cenX;
|
|
uint16 cenY = dSprite->_cenY;
|
|
uint16 dY = dSprite->_images[img]._deltaY;
|
|
uint16 height = dSprite->_images[img]._rectH;
|
|
|
|
uint16 skipY = 0;
|
|
uint16 pointIndex = 0; // This is 'screen' and 'screen + 2' in the source
|
|
|
|
pointX -= cenX;
|
|
pointY -= cenY;
|
|
pointY += dY;
|
|
|
|
// 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 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);
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace Immortal
|