diff options
Diffstat (limited to 'engines/sci/graphics')
25 files changed, 3022 insertions, 1139 deletions
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp index 77d333a717..befa5cda18 100644 --- a/engines/sci/graphics/celobj32.cpp +++ b/engines/sci/graphics/celobj32.cpp @@ -26,13 +26,12 @@ #include "sci/graphics/celobj32.h" #include "sci/graphics/frameout.h" #include "sci/graphics/palette32.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h" #include "sci/graphics/text32.h" -#include "sci/graphics/view.h" namespace Sci { #pragma mark CelScaler + CelScaler *CelObj::_scaler = nullptr; void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { @@ -323,6 +322,10 @@ public: #pragma mark - #pragma mark CelObj - Remappers +/** + * Pixel mapper for a CelObj with transparent pixels and no + * remapping data. + */ struct MAPPER_NoMD { inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { if (pixel != skipColor) { @@ -330,25 +333,49 @@ struct MAPPER_NoMD { } } }; + +/** + * Pixel mapper for a CelObj with no transparent pixels and + * no remapping data. + */ struct MAPPER_NoMDNoSkip { inline void draw(byte *target, const byte pixel, const uint8) const { *target = pixel; } }; +/** + * Pixel mapper for a CelObj with transparent pixels, + * remapping data, and remapping enabled. + */ struct MAPPER_Map { inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { if (pixel != skipColor) { + // NOTE: For some reason, SSCI never checks if the source + // pixel is *above* the range of remaps. if (pixel < g_sci->_gfxRemap32->getStartColor()) { *target = pixel; - } else { - if (g_sci->_gfxRemap32->remapEnabled(pixel)) - *target = g_sci->_gfxRemap32->remapColor(pixel, *target); + } else if (g_sci->_gfxRemap32->remapEnabled(pixel)) { + *target = g_sci->_gfxRemap32->remapColor(pixel, *target); } } } }; +/** + * Pixel mapper for a CelObj with transparent pixels, + * remapping data, and remapping disabled. + */ +struct MAPPER_NoMap { + inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { + // NOTE: For some reason, SSCI never checks if the source + // pixel is *above* the range of remaps. + if (pixel != skipColor && pixel < g_sci->_gfxRemap32->getStartColor()) { + *target = pixel; + } + } +}; + void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const { const Common::Point &scaledPosition = screenItem._scaledPosition; const Ratio &scaleX = screenItem._ratioX; @@ -523,6 +550,7 @@ void CelObj::submitPalette() const { #pragma mark - #pragma mark CelObj - Caching + int CelObj::_nextCacheId = 1; CelCache *CelObj::_cache = nullptr; @@ -624,33 +652,35 @@ void dummyFill(Buffer &target, const Common::Rect &targetRect) { } void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("drawHzFlip"); - dummyFill(target, targetRect); + render<MAPPER_NoMap, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition); } void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("drawNoFlip"); - dummyFill(target, targetRect); + render<MAPPER_NoMap, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition); } void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("drawUncompNoFlip"); - dummyFill(target, targetRect); + render<MAPPER_NoMap, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition); } void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("drawUncompHzFlip"); - dummyFill(target, targetRect); + render<MAPPER_NoMap, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition); } void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("scaleDraw"); - dummyFill(target, targetRect); + if (_drawMirrored) { + render<MAPPER_NoMap, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } else { + render<MAPPER_NoMap, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("scaleDrawUncomp"); - dummyFill(target, targetRect); + if (_drawMirrored) { + render<MAPPER_NoMap, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } else { + render<MAPPER_NoMap, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { @@ -670,17 +700,19 @@ void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, } void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - if (_drawMirrored) + if (_drawMirrored) { render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); - else + } else { render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - if (_drawMirrored) + if (_drawMirrored) { render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); - else + } else { render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { @@ -715,14 +747,16 @@ void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &sca } void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - if (_drawMirrored) + if (_drawMirrored) { render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); - else + } else { render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } #pragma mark - #pragma mark CelObjView + CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) { _info.type = kCelTypeView; _info.resourceId = viewId; @@ -765,8 +799,8 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int if (_scaledWidth == 0 || _scaledHeight == 0) { byte sizeFlag = data[5]; if (sizeFlag == 0) { - _scaledWidth = 320; - _scaledHeight = 200; + _scaledWidth = kLowResX; + _scaledHeight = kLowResY; } else if (sizeFlag == 1) { _scaledWidth = 640; _scaledHeight = 480; @@ -840,8 +874,12 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int bool CelObjView::analyzeUncompressedForRemap() const { byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); for (int i = 0; i < _width * _height; ++i) { - byte pixel = pixels[i]; - if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) { + const byte pixel = pixels[i]; + if ( + pixel >= g_sci->_gfxRemap32->getStartColor() && + pixel <= g_sci->_gfxRemap32->getEndColor() && + pixel != _transparentColor + ) { return true; } } @@ -853,8 +891,12 @@ bool CelObjView::analyzeForRemap() const { for (int y = 0; y < _height; y++) { const byte *curRow = reader.getRow(y); for (int x = 0; x < _width; x++) { - byte pixel = curRow[x]; - if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) { + const byte pixel = curRow[x]; + if ( + pixel >= g_sci->_gfxRemap32->getStartColor() && + pixel <= g_sci->_gfxRemap32->getEndColor() && + pixel != _transparentColor + ) { return true; } } @@ -881,6 +923,7 @@ byte *CelObjView::getResPointer() const { #pragma mark - #pragma mark CelObjPic + CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _info.type = kCelTypePic; _info.resourceId = picId; @@ -942,8 +985,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _scaledWidth = sizeFlag1; _scaledHeight = sizeFlag2; } else if (sizeFlag1 == 0) { - _scaledWidth = 320; - _scaledHeight = 200; + _scaledWidth = kLowResX; + _scaledHeight = kLowResY; } else if (sizeFlag1 == 1) { _scaledWidth = 640; _scaledHeight = 480; @@ -1002,6 +1045,7 @@ byte *CelObjPic::getResPointer() const { #pragma mark - #pragma mark CelObjMem + CelObjMem::CelObjMem(const reg_t bitmapObject) { _info.type = kCelTypeMem; _info.bitmap = bitmapObject; @@ -1031,6 +1075,7 @@ byte *CelObjMem::getResPointer() const { #pragma mark - #pragma mark CelObjColor + CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) { _info.type = kCelTypeColor; _info.color = color; diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h index 6e401b3df4..e405592b5f 100644 --- a/engines/sci/graphics/celobj32.h +++ b/engines/sci/graphics/celobj32.h @@ -31,6 +31,20 @@ namespace Sci { typedef Common::Rational Ratio; +// SCI32 has four different coordinate systems: +// 1. low resolution, 2. game/script resolution, +// 3. text/bitmap resolution, 4. screen resolution +// +// In CelObj, these values are used when there is +// no baked in resolution of cels. +// +// In ScreenItem, it is used when deciding which +// path to take to calculate dimensions. +enum { + kLowResX = 320, + kLowResY = 200 +}; + enum CelType { kCelTypeView = 0, kCelTypePic = 1, diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index faf1d7d1a2..61dfbedfc5 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -21,7 +21,8 @@ */ #include "common/system.h" - +#include "common/translation.h" +#include "gui/message.h" #include "sci/sci.h" #include "sci/console.h" #include "sci/event.h" @@ -41,7 +42,31 @@ GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *tex _gfxCache(cache), _gfxText32(text), _overwriteMode(false), - _nextCursorFlashTick(0) {} + _nextCursorFlashTick(0), + // SSCI used a memory handle for a ScrollWindow object + // as ID. We use a simple numeric handle instead. + _nextScrollWindowId(10000) {} + +GfxControls32::~GfxControls32() { + ScrollWindowMap::iterator it; + for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it) + delete it->_value; +} + +#pragma mark - +#pragma mark Garbage collection + +Common::Array<reg_t> GfxControls32::listObjectReferences() { + Common::Array<reg_t> ret; + ScrollWindowMap::const_iterator it; + for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it) + ret.push_back(it->_value->getBitmap()); + + return ret; +} + +#pragma mark - +#pragma mark Text input control reg_t GfxControls32::kernelEditText(const reg_t controlObject) { SegManager *segMan = _segMan; @@ -350,4 +375,470 @@ void GfxControls32::flashCursor(TextEditor &editor) { _nextCursorFlashTick = g_sci->getTickCount() + 30; } } + +#pragma mark - +#pragma mark Scrollable window control + +ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) : + _gfxText32(segMan, g_sci->_gfxCache), + _maxNumEntries(maxNumEntries), + _firstVisibleChar(0), + _topVisibleLine(0), + _lastVisibleChar(0), + _bottomVisibleLine(0), + _numLines(0), + _numVisibleLines(0), + _plane(plane), + _foreColor(defaultForeColor), + _backColor(defaultBackColor), + _borderColor(defaultBorderColor), + _fontId(defaultFontId), + _alignment(defaultAlignment), + _visible(false), + _position(position), + _screenItem(nullptr), + _nextEntryId(1) { + + _entries.reserve(maxNumEntries); + + _gfxText32.setFont(_fontId); + _pointSize = _gfxText32._font->getHeight(); + + const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + Common::Rect bitmapRect(gameRect); + mulinc(bitmapRect, Ratio(_gfxText32._scaledWidth, scriptWidth), Ratio(_gfxText32._scaledHeight, scriptHeight)); + + _textRect.left = 2; + _textRect.top = 2; + _textRect.right = bitmapRect.width() - 2; + _textRect.bottom = bitmapRect.height() - 2; + + uint8 skipColor = 0; + while (skipColor == _foreColor || skipColor == _backColor) { + skipColor++; + } + + assert(bitmapRect.width() > 0 && bitmapRect.height() > 0); + _bitmap = _gfxText32.createFontBitmap(bitmapRect.width(), bitmapRect.height(), _textRect, "", _foreColor, _backColor, skipColor, _fontId, _alignment, _borderColor, false, false); + + debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap)); +} + +ScrollWindow::~ScrollWindow() { + // _gfxText32._bitmap will get GCed once ScrollWindow is gone. + // _screenItem will be deleted by GfxFrameout +} + +Ratio ScrollWindow::where() const { + return Ratio(_topVisibleLine, MAX(_numLines, 1)); +} + +void ScrollWindow::show() { + if (_visible) { + return; + } + + if (_screenItem == nullptr) { + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _bitmap; + + _screenItem = new ScreenItem(_plane, celInfo, _position, ScaleInfo()); + } + + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane); + plane->_screenItemList.add(_screenItem); + + _visible = true; +} + +void ScrollWindow::hide() { + if (!_visible) { + return; + } + + g_sci->_gfxFrameout->deleteScreenItem(_screenItem, _plane); + _screenItem = nullptr; + g_sci->_gfxFrameout->frameOut(true); + + _visible = false; +} + +reg_t ScrollWindow::add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) { + if (_entries.size() == _maxNumEntries) { + ScrollWindowEntry removedEntry = _entries.remove_at(0); + _text.erase(0, removedEntry.text.size()); + // `_firstVisibleChar` will be reset shortly if + // `scrollTo` is true, so there is no reason to + // update it + if (!scrollTo) { + _firstVisibleChar -= removedEntry.text.size(); + } + } + + _entries.push_back(ScrollWindowEntry()); + ScrollWindowEntry &entry = _entries.back(); + + // NOTE: In SSCI the line ID was a memory handle for the + // string of this line. We use a numeric ID instead. + entry.id = make_reg(0, _nextEntryId++); + + if (_nextEntryId > _maxNumEntries) { + _nextEntryId = 1; + } + + // NOTE: In SSCI this was updated after _text was + // updated, which meant there was an extra unnecessary + // subtraction operation (subtracting `entry.text` size) + if (scrollTo) { + _firstVisibleChar = _text.size(); + } + + fillEntry(entry, text, fontId, foreColor, alignment); + _text += entry.text; + + computeLineIndices(); + update(true); + + return entry.id; +} + +void ScrollWindow::fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment) { + entry.alignment = alignment; + entry.foreColor = foreColor; + entry.fontId = fontId; + + Common::String formattedText; + + // NB: There are inconsistencies here. + // If there is a multi-line entry with non-default properties, and it + // is only partially displayed, it may not be displayed right, since the + // property directives are only added to the first line. + // (Verified by trying this in SSCI SQ6 with a custom ScrollWindowAdd call.) + // + // The converse is also a potential issue (but unverified), where lines + // with properties -1 can inherit properties from the previously rendered + // line instead of the defaults. + + // NOTE: SSCI added "|s<lineIndex>|" here, but |s| is + // not a valid control code, so it just always ended up + // getting skipped + if (entry.fontId != -1) { + formattedText += Common::String::format("|f%d|", entry.fontId); + } + if (entry.foreColor != -1) { + formattedText += Common::String::format("|c%d|", entry.foreColor); + } + if (entry.alignment != -1) { + formattedText += Common::String::format("|a%d|", entry.alignment); + } + formattedText += text; + entry.text = formattedText; +} + +reg_t ScrollWindow::modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) { + + EntriesList::iterator it = _entries.begin(); + uint firstCharLocation = 0; + for ( ; it != _entries.end(); ++it) { + if (it->id == id) { + break; + } + firstCharLocation += it->text.size(); + } + + if (it == _entries.end()) { + return make_reg(0, 0); + } + + ScrollWindowEntry &entry = *it; + + uint oldTextLength = entry.text.size(); + + fillEntry(entry, text, fontId, foreColor, alignment); + _text.replace(firstCharLocation, oldTextLength, entry.text); + + if (scrollTo) { + _firstVisibleChar = firstCharLocation; + } + + computeLineIndices(); + update(true); + + return entry.id; +} + +void ScrollWindow::upArrow() { + if (_topVisibleLine == 0) { + return; + } + + _topVisibleLine--; + _bottomVisibleLine--; + + if (_bottomVisibleLine - _topVisibleLine + 1 < _numVisibleLines) { + _bottomVisibleLine = _numLines - 1; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; + + _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + + Common::String lineText(_text.c_str() + _startsOfLines[_topVisibleLine], _text.c_str() + _startsOfLines[_topVisibleLine + 1] - 1); + + debugC(3, kDebugLevelGraphics, "ScrollWindow::upArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str()); + + _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollUp); + + if (_visible) { + assert(_screenItem); + + _screenItem->update(); + g_sci->_gfxFrameout->frameOut(true); + } +} + +void ScrollWindow::downArrow() { + if (_topVisibleLine + 1 >= _numLines) { + return; + } + + _topVisibleLine++; + _bottomVisibleLine++; + + if (_bottomVisibleLine + 1 >= _numLines) { + _bottomVisibleLine = _numLines - 1; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; + + _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + + Common::String lineText; + if (_bottomVisibleLine - _topVisibleLine + 1 == _numVisibleLines) { + lineText = Common::String(_text.c_str() + _startsOfLines[_bottomVisibleLine], _text.c_str() + _startsOfLines[_bottomVisibleLine + 1] - 1); + } else { + // scroll in empty string + } + + debugC(3, kDebugLevelGraphics, "ScrollWindow::downArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str()); + + + _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollDown); + + if (_visible) { + assert(_screenItem); + + _screenItem->update(); + g_sci->_gfxFrameout->frameOut(true); + } +} + +void ScrollWindow::go(const Ratio location) { + const int line = (location * _numLines).toInt(); + if (line < 0 || line > _numLines) { + error("Index is Out of Range in ScrollWindow"); + } + + _firstVisibleChar = _startsOfLines[line]; + update(true); + + // HACK: + // It usually isn't possible to set _topVisibleLine >= _numLines, and so + // update() doesn't. However, in this case we should set _topVisibleLine + // past the end. This is clearly visible in Phantasmagoria when dragging + // the slider in the About dialog to the very end. The slider ends up lower + // than where it can be moved by scrolling down with the arrows. + if (location.isOne()) { + _topVisibleLine = _numLines; + } +} + +void ScrollWindow::home() { + if (_firstVisibleChar == 0) { + return; + } + + _firstVisibleChar = 0; + update(true); +} + +void ScrollWindow::end() { + if (_bottomVisibleLine + 1 >= _numLines) { + return; + } + + int line = _numLines - _numVisibleLines; + if (line < 0) { + line = 0; + } + _firstVisibleChar = _startsOfLines[line]; + update(true); +} + +void ScrollWindow::pageUp() { + if (_topVisibleLine == 0) { + return; + } + + _topVisibleLine -= _numVisibleLines; + if (_topVisibleLine < 0) { + _topVisibleLine = 0; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + update(true); +} + +void ScrollWindow::pageDown() { + if (_topVisibleLine + 1 >= _numLines) { + return; + } + + _topVisibleLine += _numVisibleLines; + if (_topVisibleLine + 1 >= _numLines) { + _topVisibleLine = _numLines - 1; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + update(true); +} + +void ScrollWindow::computeLineIndices() { + _gfxText32.setFont(_fontId); + // NOTE: Unlike SSCI, foreColor and alignment are not + // set since these properties do not affect the width of + // lines + + if (_gfxText32._font->getHeight() != _pointSize) { + error("Illegal font size font = %d pointSize = %d, should be %d.", _fontId, _gfxText32._font->getHeight(), _pointSize); + } + + Common::Rect lineRect(0, 0, _textRect.width(), _pointSize + 3); + + _startsOfLines.clear(); + + // NOTE: The original engine had a 1000-line limit; we + // do not enforce any limit + for (uint charIndex = 0; charIndex < _text.size(); ) { + _startsOfLines.push_back(charIndex); + charIndex += _gfxText32.getTextCount(_text, charIndex, lineRect, false); + } + + _numLines = _startsOfLines.size(); + + _startsOfLines.push_back(_text.size()); + + _lastVisibleChar = _gfxText32.getTextCount(_text, 0, _fontId, _textRect, false) - 1; + + _bottomVisibleLine = 0; + while ( + _bottomVisibleLine < _numLines - 1 && + _startsOfLines[_bottomVisibleLine + 1] < _lastVisibleChar + ) { + ++_bottomVisibleLine; + } + + _numVisibleLines = _bottomVisibleLine + 1; +} + +void ScrollWindow::update(const bool doFrameOut) { + _topVisibleLine = 0; + while ( + _topVisibleLine < _numLines - 1 && + _firstVisibleChar >= _startsOfLines[_topVisibleLine + 1] + ) { + ++_topVisibleLine; + } + + _bottomVisibleLine = _topVisibleLine + _numVisibleLines - 1; + if (_bottomVisibleLine >= _numLines) { + _bottomVisibleLine = _numLines - 1; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + + if (_bottomVisibleLine >= 0) { + _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; + } else { + _lastVisibleChar = -1; + } + + _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + + _gfxText32.erase(_textRect, false); + _gfxText32.drawTextBox(_visibleText); + + if (_visible) { + assert(_screenItem); + + _screenItem->update(); + if (doFrameOut) { + g_sci->_gfxFrameout->frameOut(true); + } + } +} + +reg_t GfxControls32::makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) { + + ScrollWindow *scrollWindow = new ScrollWindow(_segMan, gameRect, position, planeObj, defaultForeColor, defaultBackColor, defaultFontId, defaultAlignment, defaultBorderColor, maxNumEntries); + + const uint16 id = _nextScrollWindowId++; + _scrollWindows[id] = scrollWindow; + return make_reg(0, id); +} + +ScrollWindow *GfxControls32::getScrollWindow(const reg_t id) { + ScrollWindowMap::iterator it; + it = _scrollWindows.find(id.toUint16()); + if (it == _scrollWindows.end()) + error("Invalid ScrollWindow ID"); + + return it->_value; +} + +void GfxControls32::destroyScrollWindow(const reg_t id) { + ScrollWindow *scrollWindow = getScrollWindow(id); + scrollWindow->hide(); + _scrollWindows.erase(id.getOffset()); + delete scrollWindow; +} + +#pragma mark - +#pragma mark Message box + +int16 GfxControls32::showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue) { + GUI::MessageDialog dialog(message, okLabel, altLabel); + return (dialog.runModal() == GUI::kMessageOK) ? okValue : altValue; +} + +reg_t GfxControls32::kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style) { + if (g_engine) { + g_engine->pauseEngine(true); + } + + int16 result; + + switch (style & 0xF) { + case kMessageBoxOK: + result = showMessageBox(message, _("OK"), NULL, 1, 1); + break; + case kMessageBoxYesNo: + result = showMessageBox(message, _("Yes"), _("No"), 6, 7); + break; + default: + error("Unsupported MessageBox style 0x%x", style & 0xF); + } + + if (g_engine) { + g_engine->pauseEngine(false); + } + + return make_reg(0, result); +} + } // End of namespace Sci diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h index 1bb7679ddd..460b0b5625 100644 --- a/engines/sci/graphics/controls32.h +++ b/engines/sci/graphics/controls32.h @@ -23,12 +23,19 @@ #ifndef SCI_GRAPHICS_CONTROLS32_H #define SCI_GRAPHICS_CONTROLS32_H +#include "sci/graphics/text32.h" + namespace Sci { class GfxCache; class GfxScreen; class GfxText32; +enum MessageBoxStyle { + kMessageBoxOK = 0x0, + kMessageBoxYesNo = 0x4 +}; + struct TextEditor { /** * The bitmap where the editor is rendered. @@ -100,24 +107,403 @@ struct TextEditor { }; /** + * A single block of text written to a ScrollWindow. + */ +struct ScrollWindowEntry { + /** + * ID of the line. In SSCI this was actually a memory + * handle for the string of this line. We use a simple + * numeric ID instead. + */ + reg_t id; + + /** + * The alignment to use when rendering this line of + * text. If -1, the default alignment from the + * corresponding ScrollWindow will be used. + */ + TextAlign alignment; + + /** + * The color to use to render this line of text. If -1, + * the default foreground color from the corresponding + * ScrollWindow will be used. + */ + int16 foreColor; + + /** + * The font to use to render this line of text. If -1, + * the default font from the corresponding ScrollWindow + * will be used. + */ + GuiResourceId fontId; + + /** + * The text. + */ + Common::String text; +}; + +class ScreenItem; + +/** + * A scrollable text window. + */ +class ScrollWindow { +public: + ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries); + ~ScrollWindow(); + + /** + * Adds a new text entry to the window. If `fontId`, + * `foreColor`, or `alignment` are `-1`, the + * ScrollWindow's default values will be used. + */ + reg_t add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo); + + /** + * Modifies an existing text entry with the given ID. If + * `fontId`, `foreColor`, or `alignment` are `-1`, the + * ScrollWindow's default values will be used. + */ + reg_t modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo); + + /** + * Shows the ScrollWindow if it is not already visible. + */ + void show(); + + /** + * Hides the ScrollWindow if it is currently visible. + */ + void hide(); + + /** + * Gets the number of lines that the content of a + * ScrollWindow is scrolled upward, as a ratio of the + * total number of lines of content. + */ + Ratio where() const; + + /** + * Scrolls the window to a specific location. + */ + void go(const Ratio location); + + /** + * Scrolls the window to the top. + */ + void home(); + + /** + * Scrolls the window to the bottom. + */ + void end(); + + /** + * Scrolls the window up one line. + */ + void upArrow(); + + /** + * Scrolls the window down one line. + */ + void downArrow(); + + /** + * Scrolls the window up by one page. + */ + void pageUp(); + + /** + * Scrolls the window down by one page. + */ + void pageDown(); + + /** + * Gets a reference to the in-memory bitmap that + * is used to render the text in the ScrollWindow. + */ + const reg_t getBitmap() const { return _bitmap; } + +private: + typedef Common::Array<ScrollWindowEntry> EntriesList; + + /** + * A convenience function that fills a + * ScrollWindowEntry's properties. + */ + void fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment); + + /** + * Rescans the entire text of the ScrollWindow when an + * entry is added or modified, calculating the character + * offsets of all line endings, the total number of + * lines of text, the height of the viewport (in lines + * of text), the last character visible in the viewport + * (assuming the viewport is scrolled to the top), and + * the line index of the bottommost visible line + * (assuming the viewport is scrolled to the top). + */ + void computeLineIndices(); + + /** + * Calculates which text is visible within the + * ScrollWindow's viewport and renders the text to the + * internal bitmap. + * + * If `doFrameOut` is true, the screen will be refreshed + * immediately instead of waiting for the next call to + * `kFrameOut`. + */ + void update(const bool doFrameOut); + + /** + * The text renderer. + */ + GfxText32 _gfxText32; + + /** + * The individual text entries added to the + * ScrollWindow. + */ + EntriesList _entries; + + /** + * The maximum number of entries allowed. Once this + * limit is reached, the oldest entry will be removed + * when a new entry is added. + */ + uint _maxNumEntries; + + /** + * A mapping from a line index to the line's character + * offset in `_text`. + */ + Common::Array<int> _startsOfLines; + + /** + * All text added to the window. + */ + Common::String _text; + + /** + * Text that is within the viewport of the ScrollWindow. + */ + Common::String _visibleText; + + /** + * The offset of the first visible character in `_text`. + */ + int _firstVisibleChar; + + /** + * The index of the line that is at the top of the + * viewport. + */ + int _topVisibleLine; + + /** + * The index of the last visible character in `_text`, + * or -1 if there is no text. + */ + int _lastVisibleChar; + + /** + * The index of the line that is at the bottom of the + * viewport, or -1 if there is no text. + */ + int _bottomVisibleLine; + + /** + * The total number of lines in the backbuffer. This + * number may be higher than the total number of entries + * if an entry contains newlines. + */ + int _numLines; + + /** + * The number of lines that are currently visible in the + * text area of the window. + */ + int _numVisibleLines; + + /** + * The plane in which the ScrollWindow should be + * rendered. + */ + reg_t _plane; + + /** + * The default text color. + */ + uint8 _foreColor; + + /** + * The default background color of the text bitmap. + */ + uint8 _backColor; + + /** + * The default border color of the text bitmap. If -1, + * the viewport will have no border. + */ + int16 _borderColor; + + /** + * The default font used for rendering text into the + * ScrollWindow. + */ + GuiResourceId _fontId; + + /** + * The default text alignment used for rendering text + * into the ScrollWindow. + */ + TextAlign _alignment; + + /** + * The visibility of the ScrollWindow. + */ + bool _visible; + + /** + * The dimensions of the text box inside the font + * bitmap, in text-system coordinates. + */ + Common::Rect _textRect; + + /** + * The top-left corner of the ScrollWindow's screen + * item, in game script coordinates, relative to the + * parent plane. + */ + Common::Point _position; + + /** + * The height of the default font in screen pixels. All + * fonts rendered into the ScrollWindow must have this + * same height. + */ + uint8 _pointSize; + + /** + * The bitmap used to render text. + */ + reg_t _bitmap; + + /** + * A monotonically increasing ID used to identify + * text entries added to the ScrollWindow. + */ + uint16 _nextEntryId; + + /** + * The ScrollWindow's screen item. + */ + ScreenItem *_screenItem; +}; + +/** * Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games */ class GfxControls32 { public: GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text); - - reg_t kernelEditText(const reg_t controlObject); + ~GfxControls32(); private: SegManager *_segMan; GfxCache *_gfxCache; GfxText32 *_gfxText32; +#pragma mark - +#pragma mark Garbage collection +public: + Common::Array<reg_t> listObjectReferences(); + +#pragma mark - +#pragma mark Text input control +public: + reg_t kernelEditText(const reg_t controlObject); + +private: + /** + * If true, typing will overwrite text that already + * exists at the text cursor's current position. + */ bool _overwriteMode; + + /** + * The tick at which the text cursor should be toggled + * by `flashCursor`. + */ uint32 _nextCursorFlashTick; + + /** + * Draws the text cursor for the given editor. + */ void drawCursor(TextEditor &editor); + + /** + * Erases the text cursor for the given editor. + */ void eraseCursor(TextEditor &editor); + + /** + * Toggles the text cursor for the given editor to be + * either drawn or erased. + */ void flashCursor(TextEditor &editor); + +#pragma mark - +#pragma mark Scrollable window control +public: + /** + * Creates a new scrollable window and returns the ID + * for the new window, which is used by game scripts to + * interact with scrollable windows. + */ + reg_t makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries); + + /** + * Gets a registered ScrollWindow instance by ID. + */ + ScrollWindow *getScrollWindow(const reg_t id); + + /** + * Destroys the scroll window with the given ID. + */ + void destroyScrollWindow(const reg_t id); + +private: + typedef Common::HashMap<uint16, ScrollWindow *> ScrollWindowMap; + + /** + * Monotonically increasing ID used to identify + * ScrollWindow instances. + */ + uint16 _nextScrollWindowId; + + /** + * A lookup table for registered ScrollWindow instances. + */ + ScrollWindowMap _scrollWindows; + +#pragma mark - +#pragma mark Message box +public: + /** + * Displays an OS-level message dialog. + */ + reg_t kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style); + +private: + /** + * Convenience function for creating and showing a + * message box. + */ + int16 showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue); }; } // End of namespace Sci diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index 2d44e38390..fd37020896 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -42,15 +42,13 @@ #include "sci/graphics/coordadjuster.h" #include "sci/graphics/compare.h" #include "sci/graphics/font.h" -#include "sci/graphics/view.h" #include "sci/graphics/screen.h" #include "sci/graphics/paint32.h" #include "sci/graphics/palette32.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/remap.h" -#include "sci/graphics/text32.h" #include "sci/graphics/plane32.h" +#include "sci/graphics/remap32.h" #include "sci/graphics/screen_item32.h" +#include "sci/graphics/text32.h" #include "sci/graphics/frameout.h" #include "sci/video/robot_decoder.h" @@ -69,14 +67,12 @@ static int16 unknownCDefaults[2][16] = { /* SCI2.1mid+ */ { 0, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, 0, 7, 7, 0 } }; -GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) : +GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette) : _isHiRes(false), - _cache(cache), _palette(palette), _resMan(resMan), _screen(screen), _segMan(segMan), - _paint32(paint32), _benchmarkingFinished(false), _throttleFrameOut(true), _showStyles(nullptr), @@ -257,17 +253,52 @@ void GfxFrameout::syncWithScripts(bool addElements) { } #pragma mark - +#pragma mark Benchmarking + +bool GfxFrameout::checkForFred(const reg_t object) { + const int16 viewId = readSelectorValue(_segMan, object, SELECTOR(view)); + const SciGameId gameId = g_sci->getGameId(); + + if (gameId == GID_QFG4 && viewId == 9999) { + return true; + } + + if (gameId != GID_QFG4 && viewId == -556) { + return true; + } + + if (Common::String(_segMan->getObjectName(object)) == "fred") { + return true; + } + + return false; +} + +#pragma mark - #pragma mark Screen items +void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, Plane *plane) { + if (screenItem->_created == 0) { + screenItem->_created = 0; + screenItem->_updated = 0; + screenItem->_deleted = getScreenCount(); + } else { + plane->_screenItemList.erase(screenItem); + plane->_screenItemList.pack(); + } +} + +void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, const reg_t planeObject) { + Plane *plane = _planes.findByObject(planeObject); + deleteScreenItem(screenItem, plane); +} + void GfxFrameout::kernelAddScreenItem(const reg_t object) { // The "fred" object is used to test graphics performance; // it is impacted by framerate throttling, so disable the // throttling when this item is on the screen for the // performance check to pass. - if (!_benchmarkingFinished && ( - (int16)readSelectorValue(_segMan, object, SELECTOR(view)) == -556 || - Common::String(_segMan->getObjectName(object)) == "fred" - )) { + if (!_benchmarkingFinished && _throttleFrameOut && checkForFred(object)) { _throttleFrameOut = false; } @@ -314,10 +345,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) { // it is impacted by framerate throttling, so disable the // throttling when this item is on the screen for the // performance check to pass. - if (!_benchmarkingFinished && ( - (int16)readSelectorValue(_segMan, object, SELECTOR(view)) == -556 || - Common::String(_segMan->getObjectName(object)) == "fred" - )) { + if (!_benchmarkingFinished && checkForFred(object)) { _benchmarkingFinished = true; _throttleFrameOut = true; } @@ -335,14 +363,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) { return; } - if (screenItem->_created == 0) { - screenItem->_created = 0; - screenItem->_updated = 0; - screenItem->_deleted = getScreenCount(); - } else { - plane->_screenItemList.erase(screenItem); - plane->_screenItemList.pack(); - } + deleteScreenItem(screenItem, plane); } #pragma mark - @@ -471,7 +492,7 @@ void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pi #pragma mark - #pragma mark Rendering -void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) { +void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) { // TODO: Robot // if (_robot != nullptr) { // _robot.doRobot(); @@ -489,7 +510,7 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) remapMarkRedraw(); } - calcLists(screenItemLists, eraseLists, rect); + calcLists(screenItemLists, eraseLists, eraseRect); for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { list->sort(); @@ -532,130 +553,221 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) // } } -// Determine the parts of 'r' that aren't overlapped by 'other'. -// Returns -1 if r and other have no intersection. -// Returns number of returned parts (in outRects) otherwise. -// (In particular, this returns 0 if r is contained in other.) +/** + * Determines the parts of `r` that aren't overlapped by `other`. + * Returns -1 if `r` and `other` have no intersection. + * Returns number of returned parts (in `outRects`) otherwise. + * (In particular, this returns 0 if `r` is contained in `other`.) + */ int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) { if (!r.intersects(other)) { return -1; } - int count = 0; + int splitCount = 0; if (r.top < other.top) { - Common::Rect &t = outRects[count++]; + Common::Rect &t = outRects[splitCount++]; t = r; t.bottom = other.top; r.top = other.top; } if (r.bottom > other.bottom) { - Common::Rect &t = outRects[count++]; + Common::Rect &t = outRects[splitCount++]; t = r; t.top = other.bottom; r.bottom = other.bottom; } if (r.left < other.left) { - Common::Rect &t = outRects[count++]; + Common::Rect &t = outRects[splitCount++]; t = r; t.right = other.left; r.left = other.left; } if (r.right > other.right) { - Common::Rect &t = outRects[count++]; + Common::Rect &t = outRects[splitCount++]; t = r; t.left = other.right; } - return count; + return splitCount; } -void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) { - RectList rectlist; - Common::Rect outRects[4]; +/** + * Determines the parts of `middleRect` that aren't overlapped + * by `showRect`, optimised for contiguous memory writes. + * Returns -1 if `middleRect` and `showRect` have no intersection. + * Returns number of returned parts (in `outRects`) otherwise. + * (In particular, this returns 0 if `middleRect` is contained + * in `other`.) + * + * `middleRect` is modified directly to extend into the upper + * and lower rects. + */ +int splitRectsForRender(Common::Rect &middleRect, const Common::Rect &showRect, Common::Rect(&outRects)[2]) { + if (!middleRect.intersects(showRect)) { + return -1; + } + + const int16 minLeft = MIN(middleRect.left, showRect.left); + const int16 maxRight = MAX(middleRect.right, showRect.right); + + int16 upperLeft, upperTop, upperRight, upperMaxTop; + if (middleRect.top < showRect.top) { + upperLeft = middleRect.left; + upperTop = middleRect.top; + upperRight = middleRect.right; + upperMaxTop = showRect.top; + } + else { + upperLeft = showRect.left; + upperTop = showRect.top; + upperRight = showRect.right; + upperMaxTop = middleRect.top; + } + + int16 lowerLeft, lowerRight, lowerBottom, lowerMinBottom; + if (middleRect.bottom > showRect.bottom) { + lowerLeft = middleRect.left; + lowerRight = middleRect.right; + lowerBottom = middleRect.bottom; + lowerMinBottom = showRect.bottom; + } else { + lowerLeft = showRect.left; + lowerRight = showRect.right; + lowerBottom = showRect.bottom; + lowerMinBottom = middleRect.bottom; + } + + int splitCount = 0; + middleRect.left = minLeft; + middleRect.top = upperMaxTop; + middleRect.right = maxRight; + middleRect.bottom = lowerMinBottom; + + if (upperTop != upperMaxTop) { + Common::Rect &upperRect = outRects[0]; + upperRect.left = upperLeft; + upperRect.top = upperTop; + upperRect.right = upperRight; + upperRect.bottom = upperMaxTop; + + // Merge upper rect into middle rect if possible + if (upperRect.left == middleRect.left && upperRect.right == middleRect.right) { + middleRect.top = upperRect.top; + } else { + ++splitCount; + } + } + + if (lowerBottom != lowerMinBottom) { + Common::Rect &lowerRect = outRects[splitCount]; + lowerRect.left = lowerLeft; + lowerRect.top = lowerMinBottom; + lowerRect.right = lowerRight; + lowerRect.bottom = lowerBottom; + + // Merge lower rect into middle rect if possible + if (lowerRect.left == middleRect.left && lowerRect.right == middleRect.right) { + middleRect.bottom = lowerRect.bottom; + } else { + ++splitCount; + } + } + + assert(splitCount <= 2); + return splitCount; +} +// NOTE: The third rectangle parameter is only ever given a non-empty rect +// by VMD code, via `frameOut` +void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect) { + RectList eraseList; + Common::Rect outRects[4]; int deletedPlaneCount = 0; - bool addedToRectList = false; - int planeCount = _planes.size(); + bool addedToEraseList = false; bool foundTransparentPlane = false; - if (!calcRect.isEmpty()) { - addedToRectList = true; - rectlist.add(calcRect); + if (!eraseRect.isEmpty()) { + addedToEraseList = true; + eraseList.add(eraseRect); } + PlaneList::size_type planeCount = _planes.size(); for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) { - Plane *outerPlane = _planes[outerPlaneIndex]; + const Plane *outerPlane = _planes[outerPlaneIndex]; + const Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object); + // NOTE: SSCI only ever checks for kPlaneTypeTransparent here, even + // though kPlaneTypeTransparentPicture is also a transparent plane if (outerPlane->_type == kPlaneTypeTransparent) { foundTransparentPlane = true; } - Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object); - if (outerPlane->_deleted) { - if (visiblePlane != nullptr) { - if (!visiblePlane->_screenRect.isEmpty()) { - addedToRectList = true; - rectlist.add(visiblePlane->_screenRect); - } + if (visiblePlane != nullptr && !visiblePlane->_screenRect.isEmpty()) { + eraseList.add(visiblePlane->_screenRect); + addedToEraseList = true; } ++deletedPlaneCount; - } else if (visiblePlane != nullptr) { - if (outerPlane->_updated) { - --outerPlane->_updated; - - int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects); - if (splitcount) { - if (splitcount == -1) { - if (!visiblePlane->_screenRect.isEmpty()) { - rectlist.add(visiblePlane->_screenRect); - } + } else if (visiblePlane != nullptr && outerPlane->_moved) { + // _moved will be decremented in the final loop through the planes, + // at the end of this function + + { + const int splitCount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects); + if (splitCount) { + if (splitCount == -1 && !visiblePlane->_screenRect.isEmpty()) { + eraseList.add(visiblePlane->_screenRect); } else { - for (int i = 0; i < splitcount; ++i) { - rectlist.add(outRects[i]); + for (int i = 0; i < splitCount; ++i) { + eraseList.add(outRects[i]); } } - - addedToRectList = true; + addedToEraseList = true; } + } - if (!outerPlane->_redrawAllCount) { - int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects); - if (splitCount) { - for (int i = 0; i < splitCount; ++i) { - rectlist.add(outRects[i]); - } - addedToRectList = true; + if (!outerPlane->_redrawAllCount) { + const int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects); + if (splitCount) { + for (int i = 0; i < splitCount; ++i) { + eraseList.add(outRects[i]); } + addedToEraseList = true; } } } - if (addedToRectList) { - for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) { - for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) { - Plane *innerPlane = _planes[innerPlaneIndex]; - - if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) { - if (innerPlane->_redrawAllCount == 0) { - eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect)); + if (addedToEraseList) { + for (int rectIndex = 0; rectIndex < eraseList.size(); ++rectIndex) { + const Common::Rect &rect = *eraseList[rectIndex]; + for (int innerPlaneIndex = planeCount - 1; innerPlaneIndex >= 0; --innerPlaneIndex) { + const Plane &innerPlane = *_planes[innerPlaneIndex]; + + if ( + !innerPlane._deleted && + innerPlane._type != kPlaneTypeTransparent && + innerPlane._screenRect.intersects(rect) + ) { + if (!innerPlane._redrawAllCount) { + eraseLists[innerPlaneIndex].add(innerPlane._screenRect.findIntersectingRect(rect)); } - int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects); + const int splitCount = splitRects(rect, innerPlane._screenRect, outRects); for (int i = 0; i < splitCount; ++i) { - rectlist.add(outRects[i]); + eraseList.add(outRects[i]); } - rectlist.erase(rect); + eraseList.erase_at(rectIndex); break; } } } - rectlist.pack(); + eraseList.pack(); } } @@ -667,9 +779,9 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL if (plane->_deleted) { --plane->_deleted; if (plane->_deleted <= 0) { - PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object)); - if (visiblePlaneIt != _visiblePlanes.end()) { - _visiblePlanes.erase(visiblePlaneIt); + const int visiblePlaneIndex = _visiblePlanes.findIndexByObject(plane->_object); + if (visiblePlaneIndex != -1) { + _visiblePlanes.remove_at(visiblePlaneIndex); } _planes.remove_at(planeIndex); @@ -684,107 +796,114 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL } } + // Some planes may have been deleted, so re-retrieve count planeCount = _planes.size(); - for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) { + + for (PlaneList::size_type outerIndex = 0; outerIndex < planeCount; ++outerIndex) { // "outer" just refers to the outer loop - Plane *outerPlane = _planes[outerIndex]; - if (outerPlane->_priorityChanged) { - --outerPlane->_priorityChanged; + Plane &outerPlane = *_planes[outerIndex]; + if (outerPlane._priorityChanged) { + --outerPlane._priorityChanged; - Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object); + const Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane._object); if (visibleOuterPlane == nullptr) { - warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane->_object)); + warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane._object)); continue; } - rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect)); + eraseList.add(outerPlane._screenRect.findIntersectingRect(visibleOuterPlane->_screenRect)); - for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) { + for (PlaneList::size_type innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) { // "inner" just refers to the inner loop - Plane *innerPlane = _planes[innerIndex]; - Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object); - - int rectCount = rectlist.size(); - for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) { - int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects); + const Plane &innerPlane = *_planes[innerIndex]; + const Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane._object); + const RectList::size_type rectCount = eraseList.size(); + for (RectList::size_type rectIndex = 0; rectIndex < rectCount; ++rectIndex) { + const int splitCount = splitRects(*eraseList[rectIndex], innerPlane._screenRect, outRects); if (splitCount == 0) { if (visibleInnerPlane != nullptr) { // same priority, or relative priority between inner/outer changed - if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) { - if (outerPlane->_priority <= innerPlane->_priority) { - eraseLists[innerIndex].add(*rectlist[rectIndex]); + if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) { + if (outerPlane._priority <= innerPlane._priority) { + eraseLists[innerIndex].add(*eraseList[rectIndex]); } else { - eraseLists[outerIndex].add(*rectlist[rectIndex]); + eraseLists[outerIndex].add(*eraseList[rectIndex]); } } } - rectlist.erase_at(rectIndex); + eraseList.erase_at(rectIndex); } else if (splitCount != -1) { for (int i = 0; i < splitCount; ++i) { - rectlist.add(outRects[i]); + eraseList.add(outRects[i]); } if (visibleInnerPlane != nullptr) { // same priority, or relative priority between inner/outer changed - if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) { - *rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect); - if (outerPlane->_priority <= innerPlane->_priority) { - eraseLists[innerIndex].add(*rectlist[rectIndex]); - } - else { - eraseLists[outerIndex].add(*rectlist[rectIndex]); + if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) { + *eraseList[rectIndex] = outerPlane._screenRect.findIntersectingRect(innerPlane._screenRect); + + if (outerPlane._priority <= innerPlane._priority) { + eraseLists[innerIndex].add(*eraseList[rectIndex]); + } else { + eraseLists[outerIndex].add(*eraseList[rectIndex]); } } } - rectlist.erase_at(rectIndex); + eraseList.erase_at(rectIndex); } } - rectlist.pack(); + eraseList.pack(); } } } - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - Plane *plane = _planes[planeIndex]; - Plane *visiblePlane = nullptr; + for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) { + Plane &plane = *_planes[planeIndex]; + Plane *visiblePlane = _visiblePlanes.findByObject(plane._object); - PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object)); - if (visiblePlaneIt != _visiblePlanes.end()) { - visiblePlane = *visiblePlaneIt; - } + if (!plane._screenRect.isEmpty()) { + if (plane._redrawAllCount) { + plane.redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); + } else { + if (visiblePlane == nullptr) { + error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane._object)); + } - if (plane->_redrawAllCount) { - plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); - } else { - if (visiblePlane == nullptr) { - error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object)); + plane.calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); } + } else { + plane.decrementScreenItemArrayCounts(visiblePlane, false); + } - plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); + if (plane._moved) { + // the work for handling moved/resized planes was already done + // earlier in the function, we are just cleaning up now + --plane._moved; } - if (plane->_created) { - _visiblePlanes.add(new Plane(*plane)); - --plane->_created; - } else if (plane->_moved) { - assert(visiblePlaneIt != _visiblePlanes.end()); - **visiblePlaneIt = *plane; - --plane->_moved; + if (plane._created) { + _visiblePlanes.add(new Plane(plane)); + --plane._created; + } else if (plane._updated) { + *visiblePlane = plane; + --plane._updated; } } + // NOTE: SSCI only looks for kPlaneTypeTransparent, not + // kPlaneTypeTransparentPicture if (foundTransparentPlane) { - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - for (int i = planeIndex + 1; i < planeCount; ++i) { + for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) { + for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) { if (_planes[i]->_type == kPlaneTypeTransparent) { _planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]); } } if (_planes[planeIndex]->_type == kPlaneTypeTransparent) { - for (int i = planeIndex - 1; i >= 0; --i) { + for (PlaneList::size_type i = planeIndex - 1; i >= 0; --i) { _planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]); } @@ -793,7 +912,7 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL } } - for (int i = planeIndex + 1; i < planeCount; ++i) { + for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) { if (_planes[i]->_type == kPlaneTypeTransparent) { _planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]); } @@ -807,17 +926,19 @@ void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) { return; } - for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) { - mergeToShowList(**it, _showList, _overdrawThreshold); - _currentBuffer.fillRect(**it, plane._back); + const RectList::size_type eraseListSize = eraseList.size(); + for (RectList::size_type i = 0; i < eraseListSize; ++i) { + mergeToShowList(*eraseList[i], _showList, _overdrawThreshold); + _currentBuffer.fillRect(*eraseList[i], plane._back); } } void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) { - for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) { - DrawItem &drawItem = **it; + const DrawList::size_type drawListSize = screenItemList.size(); + for (DrawList::size_type i = 0; i < drawListSize; ++i) { + const DrawItem &drawItem = *screenItemList[i]; mergeToShowList(drawItem.rect, _showList, _overdrawThreshold); - ScreenItem &screenItem = *drawItem.screenItem; + const ScreenItem &screenItem = *drawItem.screenItem; // TODO: Remove // debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect)); CelObj &celObj = *screenItem._celObj; @@ -826,36 +947,65 @@ void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) { } void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) { - Common::Rect merged(drawRect); - - bool didDelete = true; - RectList::size_type count = showList.size(); - while (didDelete && count) { - didDelete = false; - - for (RectList::size_type i = 0; i < count; ++i) { - Common::Rect existing = *showList[i]; - Common::Rect candidate; - candidate.left = MIN(merged.left, existing.left); - candidate.top = MIN(merged.top, existing.top); - candidate.right = MAX(merged.right, existing.right); - candidate.bottom = MAX(merged.bottom, existing.bottom); - - if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) { - merged = candidate; - showList.erase_at(i); - didDelete = true; + RectList mergeList; + Common::Rect merged; + mergeList.add(drawRect); + + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + bool didMerge = false; + const Common::Rect &r1 = *mergeList[i]; + if (!r1.isEmpty()) { + for (RectList::size_type j = 0; j < showList.size(); ++j) { + const Common::Rect &r2 = *showList[j]; + if (!r2.isEmpty()) { + merged = r1; + merged.extend(r2); + + int difference = merged.width() * merged.height(); + difference -= r1.width() * r1.height(); + difference -= r2.width() * r2.height(); + if (r1.intersects(r2)) { + const Common::Rect overlap = r1.findIntersectingRect(r2); + difference += overlap.width() * overlap.height(); + } + + if (difference <= overdrawThreshold) { + mergeList.erase_at(i); + showList.erase_at(j); + mergeList.add(merged); + didMerge = true; + break; + } else { + Common::Rect outRects[2]; + int splitCount = splitRectsForRender(*mergeList[i], *showList[j], outRects); + if (splitCount != -1) { + mergeList.add(*mergeList[i]); + mergeList.erase_at(i); + showList.erase_at(j); + didMerge = true; + while (splitCount--) { + mergeList.add(outRects[splitCount]); + } + break; + } + } + } } - } - count = showList.pack(); + if (didMerge) { + showList.pack(); + } + } } - showList.add(merged); + mergeList.pack(); + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + showList.add(*mergeList[i]); + } } void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) { - Palette sourcePalette(*_palette->getNextPalette()); + Palette sourcePalette(_palette->getNextPalette()); alterVmap(sourcePalette, sourcePalette, -1, styleRanges); int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); @@ -864,8 +1014,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry _showList.add(rect); showBits(); - Common::Rect calcRect(0, 0); - // NOTE: The original engine allocated these as static arrays of 100 // pointers to ScreenItemList / RectList ScreenItemListList screenItemLists; @@ -878,7 +1026,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry remapMarkRedraw(); } - calcLists(screenItemLists, eraseLists, calcRect); + calcLists(screenItemLists, eraseLists); for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { list->sort(); } @@ -897,7 +1045,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry drawScreenItemList(screenItemLists[i]); } - Palette nextPalette(*_palette->getNextPalette()); + Palette nextPalette(_palette->getNextPalette()); if (prevRoom < 1000) { for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { @@ -908,6 +1056,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry } } else { for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { + // TODO: Limiting range 72 to 103 is NOT present in every game if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) { sourcePalette.colors[i] = nextPalette.colors[i]; sourcePalette.colors[i].used = true; @@ -938,7 +1087,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry remapMarkRedraw(); } - calcLists(screenItemLists, eraseLists, calcRect); + calcLists(screenItemLists, eraseLists); for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { list->sort(); } @@ -967,10 +1116,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry _frameNowVisible = true; } -// TODO: What does the bit masking for the show rects do, -// and does it cause an off-by-one error in rect calculations -// since SOL_Rect is BR inclusive and Common::Rect is BR -// exclusive? void GfxFrameout::showBits() { for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { Common::Rect rounded(**rect); diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index 0f06ee1c85..e736872773 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -149,10 +149,7 @@ struct ShowStyleEntry { typedef Common::Array<DrawList> ScreenItemListList; typedef Common::Array<RectList> EraseListList; -class GfxCache; class GfxCoordAdjuster32; -class GfxPaint32; -class GfxPalette; class GfxScreen; /** @@ -162,16 +159,14 @@ class GfxScreen; class GfxFrameout { private: bool _isHiRes; - GfxCache *_cache; GfxCoordAdjuster32 *_coordAdjuster; GfxPalette32 *_palette; ResourceManager *_resMan; GfxScreen *_screen; SegManager *_segMan; - GfxPaint32 *_paint32; public: - GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32); + GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette); ~GfxFrameout(); void clear(); @@ -194,13 +189,28 @@ private: */ bool _throttleFrameOut; + /** + * Determines whether or not a screen item is the "Fred" + * object. + */ + bool checkForFred(const reg_t object); + #pragma mark - #pragma mark Screen items private: - void deleteScreenItem(ScreenItem *screenItem, const reg_t plane); void remapMarkRedraw(); public: + /** + * Deletes a screen item from the given plane. + */ + void deleteScreenItem(ScreenItem *screenItem, Plane *plane); + + /** + * Deletes a screen item from the given plane. + */ + void deleteScreenItem(ScreenItem *screenItem, const reg_t plane); + void kernelAddScreenItem(const reg_t object); void kernelUpdateScreenItem(const reg_t object); void kernelDeleteScreenItem(const reg_t object); @@ -366,8 +376,10 @@ private: * over the entire screen for rendering the next frame. * The draw and erase lists in `drawLists` and * `eraseLists` each represent one plane on the screen. + * The optional `eraseRect` argument allows a specific + * area of the screen to be erased. */ - void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect); + void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect = Common::Rect()); /** * Erases the areas in the given erase list from the @@ -420,9 +432,10 @@ public: /** * Updates the internal screen buffer for the next * frame. If `shouldShowBits` is true, also sends the - * buffer to hardware. + * buffer to hardware. If `eraseRect` is non-empty, + * it is added to the erase list for this frame. */ - void frameOut(const bool shouldShowBits, const Common::Rect &rect = Common::Rect()); + void frameOut(const bool shouldShowBits, const Common::Rect &eraseRect = Common::Rect()); /** * Modifies the raw pixel data for the next frame with diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index 19dddd74b8..3fcc83c5e2 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -191,6 +191,12 @@ struct Buffer : public Graphics::Surface { uint16 scriptWidth; uint16 scriptHeight; + Buffer() : + screenWidth(0), + screenHeight(0), + scriptWidth(320), + scriptHeight(200) {} + Buffer(const uint16 width, const uint16 height, uint8 *const pix) : screenWidth(width), screenHeight(height), @@ -231,7 +237,7 @@ struct Color { return used == other.used && r == other.r && g == other.g && b == other.b; } inline bool operator!=(const Color &other) const { - return !(*this == other); + return !operator==(other); } #endif }; diff --git a/engines/sci/graphics/paint.cpp b/engines/sci/graphics/paint.cpp deleted file mode 100644 index 482b81aff1..0000000000 --- a/engines/sci/graphics/paint.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "graphics/primitives.h" - -#include "sci/sci.h" -#include "sci/engine/state.h" -#include "sci/engine/selector.h" -#include "sci/graphics/paint.h" - -namespace Sci { - -GfxPaint::GfxPaint() { -} - -GfxPaint::~GfxPaint() { -} - -void GfxPaint::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) { -} - -void GfxPaint::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) { -} - -} // End of namespace Sci diff --git a/engines/sci/graphics/paint.h b/engines/sci/graphics/paint.h deleted file mode 100644 index b2277131d5..0000000000 --- a/engines/sci/graphics/paint.h +++ /dev/null @@ -1,39 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef SCI_GRAPHICS_PAINT_H -#define SCI_GRAPHICS_PAINT_H - -namespace Sci { - -class GfxPaint { -public: - GfxPaint(); - virtual ~GfxPaint(); - - virtual void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); - virtual void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); -}; - -} // End of namespace Sci - -#endif diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h index 955cfdec8f..317388b2df 100644 --- a/engines/sci/graphics/paint16.h +++ b/engines/sci/graphics/paint16.h @@ -23,8 +23,6 @@ #ifndef SCI_GRAPHICS_PAINT16_H #define SCI_GRAPHICS_PAINT16_H -#include "sci/graphics/paint.h" - namespace Sci { class GfxPorts; @@ -36,7 +34,7 @@ class GfxView; /** * Paint16 class, handles painting/drawing for SCI16 (SCI0-SCI1.1) games */ -class GfxPaint16 : public GfxPaint { +class GfxPaint16 { public: GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio); ~GfxPaint16(); diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp index a210a469f1..bfd46484e9 100644 --- a/engines/sci/graphics/paint32.cpp +++ b/engines/sci/graphics/paint32.cpp @@ -20,49 +20,162 @@ * */ -#include "sci/sci.h" -#include "sci/engine/state.h" -#include "sci/engine/selector.h" -#include "sci/graphics/coordadjuster.h" -#include "sci/graphics/cache.h" +#include "graphics/primitives.h" +#include "sci/engine/seg_manager.h" #include "sci/graphics/paint32.h" -#include "sci/graphics/font.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/view.h" -#include "sci/graphics/screen.h" -#include "sci/graphics/palette.h" +#include "sci/graphics/text32.h" namespace Sci { -GfxPaint32::GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette) - : _resMan(resMan), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette) { +GfxPaint32::GfxPaint32(SegManager *segMan) : + _segMan(segMan) {} + +reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) { + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject); + if (plane == nullptr) { + error("kAddLine: Plane %04x:%04x not found", PRINT_REG(planeObject)); + } + + Common::Rect gameRect; + BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = bitmap.getObject(); + // SSCI stores the line color on `celInfo`, even though + // this is not a `kCelTypeColor`, as a hack so that + // `kUpdateLine` can get the originally used color + celInfo.color = color; + + ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, Common::Rect(startPoint.x, startPoint.y, startPoint.x + bitmap.getWidth(), startPoint.y + bitmap.getHeight())); + screenItem->_priority = priority; + screenItem->_fixedPriority = true; + + plane->_screenItemList.add(screenItem); + + return screenItem->_object; +} + +void GfxPaint32::kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) { + + Common::Rect gameRect; + BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect); + + _segMan->freeHunkEntry(screenItem->_celInfo.bitmap); + screenItem->_celInfo.bitmap = bitmap.getObject(); + screenItem->_celInfo.color = color; + screenItem->_position = startPoint; + screenItem->_priority = priority; + screenItem->update(); } -GfxPaint32::~GfxPaint32() { +void GfxPaint32::kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject) { + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject); + if (plane == nullptr) { + return; + } + + ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject); + if (screenItem == nullptr) { + return; + } + + _segMan->freeHunkEntry(screenItem->_celInfo.bitmap); + g_sci->_gfxFrameout->deleteScreenItem(screenItem, plane); } -void GfxPaint32::fillRect(Common::Rect rect, byte color) { - int16 y, x; - Common::Rect clipRect = rect; +void GfxPaint32::plotter(int x, int y, int color, void *data) { + LineProperties &properties = *static_cast<LineProperties *>(data); + byte *pixels = properties.bitmap->getPixels(); + + const uint32 index = properties.bitmap->getWidth() * y + x; + + if (index < properties.bitmap->getDataSize()) { + if (properties.solid) { + pixels[index] = (uint8)color; + return; + } + + if (properties.horizontal && x != properties.lastAddress) { + properties.lastAddress = x; + ++properties.patternIndex; + } else if (!properties.horizontal && y != properties.lastAddress) { + properties.lastAddress = y; + ++properties.patternIndex; + } - clipRect.clip(_screen->getWidth(), _screen->getHeight()); + if (properties.pattern[properties.patternIndex]) { + pixels[index] = (uint8)color; + } - for (y = clipRect.top; y < clipRect.bottom; y++) { - for (x = clipRect.left; x < clipRect.right; x++) { - _screen->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0, 0); + if (properties.patternIndex == ARRAYSIZE(properties.pattern)) { + properties.patternIndex = 0; } + } else { + warning("GfxPaint32::plotter: Attempted to write out of bounds (%u >= %u)", index, properties.bitmap->getDataSize()); } } -void GfxPaint32::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) { - GfxPicture *picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false); +BitmapResource GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, uint16 pattern, uint8 thickness, Common::Rect &outRect) { + const uint8 skipColor = color != 250 ? 250 : 0; - picture->draw(animationNr, mirroredFlag, addToFlag, EGApaletteNo); - delete picture; -} + // Thickness is expected to be 2n+1 + thickness = ((MAX((uint8)1, thickness) - 1) | 1); + const uint8 halfThickness = thickness >> 1; + + outRect.left = (startPoint.x < endPoint.x ? startPoint.x : endPoint.x) - halfThickness; + outRect.top = (startPoint.y < endPoint.y ? startPoint.y : endPoint.y) - halfThickness; + outRect.right = (startPoint.x > endPoint.x ? startPoint.x : endPoint.x) + halfThickness + 1; + outRect.bottom = (startPoint.y > endPoint.y ? startPoint.y : endPoint.y) + halfThickness + 1; + + BitmapResource bitmap(_segMan, outRect.width(), outRect.height(), skipColor, 0, 0, g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, 0, false); + + byte *pixels = bitmap.getPixels(); + memset(pixels, skipColor, bitmap.getWidth() * bitmap.getHeight()); + + LineProperties properties; + properties.bitmap = &bitmap; + + switch (style) { + case kLineStyleSolid: + pattern = 0xFFFF; + properties.solid = true; + break; + case kLineStyleDashed: + pattern = 0xFF00; + properties.solid = false; + break; + case kLineStylePattern: + properties.solid = pattern == 0xFFFF; + break; + } + + const Common::Rect drawRect( + startPoint.x - outRect.left, + startPoint.y - outRect.top, + endPoint.x - outRect.left, + endPoint.y - outRect.top + ); -void GfxPaint32::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) { - _screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control); + if (!properties.solid) { + for (int i = 0; i < ARRAYSIZE(properties.pattern); ++i) { + properties.pattern[i] = (pattern & 0x8000); + pattern <<= 1; + } + + properties.patternIndex = 0; + properties.horizontal = ABS(drawRect.right - drawRect.left) > ABS(drawRect.bottom - drawRect.top); + properties.lastAddress = properties.horizontal ? drawRect.left : drawRect.top; + } + + if (thickness <= 1) { + Graphics::drawLine(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, color, plotter, &properties); + } else { + Graphics::drawThickLine2(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, thickness, color, plotter, &properties); + } + + return bitmap; } + } // End of namespace Sci diff --git a/engines/sci/graphics/paint32.h b/engines/sci/graphics/paint32.h index e7a3ec256d..6d5a957fcd 100644 --- a/engines/sci/graphics/paint32.h +++ b/engines/sci/graphics/paint32.h @@ -23,30 +23,48 @@ #ifndef SCI_GRAPHICS_PAINT32_H #define SCI_GRAPHICS_PAINT32_H -#include "sci/graphics/paint.h" - namespace Sci { +class BitmapResource; +class Plane; +class ScreenItem; +class SegManager; -class GfxPorts; +enum LineStyle { + kLineStyleSolid, + kLineStyleDashed, + kLineStylePattern +}; /** * Paint32 class, handles painting/drawing for SCI32 (SCI2+) games */ -class GfxPaint32 : public GfxPaint { +class GfxPaint32 { public: - GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette); - ~GfxPaint32(); + GfxPaint32(SegManager *segMan); - void fillRect(Common::Rect rect, byte color); +private: + SegManager *_segMan; - void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); - void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); +#pragma mark - +#pragma mark Line drawing +public: + reg_t kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness); + void kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness); + void kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject); private: - ResourceManager *_resMan; - GfxCoordAdjuster *_coordAdjuster; - GfxScreen *_screen; - GfxPalette *_palette; + typedef struct { + BitmapResource *bitmap; + bool pattern[16]; + uint8 patternIndex; + bool solid; + bool horizontal; + int lastAddress; + } LineProperties; + + static void plotter(int x, int y, int color, void *data); + + BitmapResource makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness, Common::Rect &outRect); }; } // End of namespace Sci diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp index 6844011675..0840e82a40 100644 --- a/engines/sci/graphics/palette32.cpp +++ b/engines/sci/graphics/palette32.cpp @@ -28,7 +28,7 @@ #include "sci/event.h" #include "sci/resource.h" #include "sci/graphics/palette32.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h" #include "sci/graphics/screen.h" namespace Sci { @@ -78,10 +78,6 @@ inline void mergePaletteInternal(Palette *const to, const Palette *const from) { } } -const Palette *GfxPalette32::getNextPalette() const { - return &_nextPalette; -} - void GfxPalette32::submit(Palette &palette) { // TODO: The resource manager in SCI32 retains raw data of palettes from // the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and @@ -178,49 +174,6 @@ void GfxPalette32::set(Palette *newPalette, bool force, bool forceRealMerge) { submit(*newPalette); } -// In SCI32 engine this method is SOLPalette::Match(Rgb24 *, int, int *, int *) -// and is used by Remap -// TODO: Anything that calls GfxPalette::matchColor(int, int, int) is going to -// match using an algorithm from SCI16 engine right now. This needs to be -// corrected in the future so either nothing calls -// GfxPalette::matchColor(int, int, int), or it is fixed to match the other -// SCI32 algorithms. -int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) { - int16 bestIndex = -1; - int bestDifference = 0xFFFFF; - int difference = defaultDifference; - - // SQ6 DOS really does check only the first 236 entries - for (int i = 0, channelDifference; i < 236; ++i) { - if (matchTable[i] == 0) { - continue; - } - - difference = _sysPalette.colors[i].r - r; - difference *= difference; - if (bestDifference <= difference) { - continue; - } - channelDifference = _sysPalette.colors[i].g - g; - difference += channelDifference * channelDifference; - if (bestDifference <= difference) { - continue; - } - channelDifference = _sysPalette.colors[i].b - b; - difference += channelDifference * channelDifference; - if (bestDifference <= difference) { - continue; - } - bestDifference = difference; - bestIndex = i; - } - - // NOTE: This value is only valid if the last index to - // perform a difference calculation was the best index - lastCalculatedDifference = difference; - return bestIndex; -} - bool GfxPalette32::updateForFrame() { applyAll(); _versionUpdated = false; diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h index a5450776dc..7dda53e5c1 100644 --- a/engines/sci/graphics/palette32.h +++ b/engines/sci/graphics/palette32.h @@ -113,12 +113,12 @@ private: public: virtual void saveLoadWithSerializer(Common::Serializer &s) override; - const Palette *getNextPalette() const; + inline const Palette &getNextPalette() const { return _nextPalette; }; + inline const Palette &getCurrentPalette() const { return _sysPalette; }; bool kernelSetFromResource(GuiResourceId resourceId, bool force) override; int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override; void set(Palette *newPalette, bool force, bool forceRealMerge = false) override; - int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable); /** * Submits a palette to display. Entries marked as “used” in the @@ -133,7 +133,7 @@ public: void applyAll(); #pragma mark - -#pragma mark color look-up +#pragma mark Color look-up private: /** * An optional lookup table used to remap RGB565 colors to a palette @@ -240,6 +240,11 @@ private: * According to SCI engine code, when two cyclers overlap, * a fatal error has occurred and the engine will display * an error and then exit. + * + * The cycle map is also by the color remapping system to + * avoid attempting to remap to palette entries that are + * cycling (so won't be the expected color once the cycler + * runs again). */ bool _cycleMap[256]; inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear); @@ -257,7 +262,7 @@ public: void cycleAllOff(); void applyAllCycles(); void applyCycles(); - const bool *getCyclemap() { return _cycleMap; } + inline const bool *getCycleMap() const { return _cycleMap; } #pragma mark - #pragma mark Fading diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp index 470986fb3c..175875c414 100644 --- a/engines/sci/graphics/plane32.cpp +++ b/engines/sci/graphics/plane32.cpp @@ -27,7 +27,7 @@ #include "sci/graphics/frameout.h" #include "sci/graphics/lists32.h" #include "sci/graphics/plane32.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h" #include "sci/graphics/screen.h" #include "sci/graphics/screen_item32.h" @@ -163,11 +163,15 @@ void Plane::printDebugInfo(Console *con) const { void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) { uint16 celCount = 1000; + bool transparent = true; for (uint16 celNo = 0; celNo < celCount; ++celNo) { CelObjPic *celObj = new CelObjPic(pictureId, celNo); if (celCount == 1000) { celCount = celObj->_celCount; } + if (!celObj->_transparent) { + transparent = false; + } ScreenItem *screenItem = new ScreenItem(_object, celObj->_info); screenItem->_pictureId = pictureId; @@ -184,6 +188,7 @@ void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *p delete screenItem->_celObj; screenItem->_celObj = celObj; } + _type = transparent ? kPlaneTypeTransparentPicture : kPlaneTypePicture; } void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) { @@ -196,7 +201,7 @@ void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, void Plane::changePic() { _pictureChanged = false; - if (_type != kPlaneTypePicture) { + if (_type != kPlaneTypePicture && _type != kPlaneTypeTransparentPicture) { return; } @@ -240,16 +245,20 @@ void Plane::deleteAllPics() { #pragma mark Plane - Rendering void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const { - int index = planeList.findIndexByObject(_object); + const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1; + const PlaneList::size_type planeCount = planeList.size(); for (DrawList::size_type i = 0; i < drawList.size(); ++i) { - for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) { - if (planeList[j]->_type != kPlaneTypeTransparent) { - Common::Rect ptr[4]; - int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr); - if (count != -1) { - for (int k = count - 1; k >= 0; --k) { - drawList.add(drawList[i]->screenItem, ptr[k]); + for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) { + if ( + planeList[j]->_type != kPlaneTypeTransparent && + planeList[j]->_type != kPlaneTypeTransparentPicture + ) { + Common::Rect outRects[4]; + int splitCount = splitRects(drawList[i]->rect, planeList[j]->_screenRect, outRects); + if (splitCount != -1) { + while (splitCount--) { + drawList.add(drawList[i]->screenItem, outRects[splitCount]); } drawList.erase_at(i); @@ -262,17 +271,20 @@ void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList } void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const { - int index = planeList.findIndexByObject(_object); + const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1; + const PlaneList::size_type planeCount = planeList.size(); for (RectList::size_type i = 0; i < eraseList.size(); ++i) { - for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) { - if (planeList[j]->_type != kPlaneTypeTransparent) { - Common::Rect ptr[4]; - - int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr); - if (count != -1) { - for (int k = count - 1; k >= 0; --k) { - eraseList.add(ptr[k]); + for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) { + if ( + planeList[j]->_type != kPlaneTypeTransparent && + planeList[j]->_type != kPlaneTypeTransparentPicture + ) { + Common::Rect outRects[4]; + int splitCount = splitRects(*eraseList[i], planeList[j]->_screenRect, outRects); + if (splitCount != -1) { + while (splitCount--) { + eraseList.add(outRects[splitCount]); } eraseList.erase_at(i); @@ -285,94 +297,109 @@ void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeLi } void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) { - ScreenItemList::size_type planeItemCount = _screenItemList.size(); - ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size(); + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + const ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size(); + + for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { + // Items can be added to ScreenItemList and we don't want to process + // those new items, but the list also can grow smaller, so we need + // to check that we are still within the upper bound of the list and + // quit if we aren't any more + if (i >= _screenItemList.size()) { + break; + } + + ScreenItem *item = _screenItemList[i]; + if (item == nullptr) { + continue; + } - for (ScreenItemList::size_type i = 0; i < planeItemCount; ++i) { - ScreenItem *vitem = nullptr; // NOTE: The original engine used an array without bounds checking // so could just get the visible screen item directly; we need to // verify that the index is actually within the valid range for // the visible plane before accessing the item to avoid a range // error. + const ScreenItem *visibleItem = nullptr; if (i < visiblePlaneItemCount) { - vitem = visiblePlane._screenItemList[i]; + visibleItem = visiblePlane._screenItemList[i]; } - ScreenItem *item = _screenItemList[i]; - if (i < _screenItemList.size() && item != nullptr) { - if (item->_deleted) { - // add item's rect to erase list - if ( - i < visiblePlane._screenItemList.size() && - vitem != nullptr && - !vitem->_screenRect.isEmpty() - ) { - if (g_sci->_gfxRemap32->getRemapCount()) { - mergeToRectList(vitem->_screenRect, eraseList); - } else { - eraseList.add(vitem->_screenRect); - } - } - } else if (item->_created) { - // add item to draw list - item->calcRects(*this); - - if(!item->_screenRect.isEmpty()) { - if (g_sci->_gfxRemap32->getRemapCount()) { - drawList.add(item, item->_screenRect); - mergeToRectList(item->_screenRect, eraseList); - } else { - drawList.add(item, item->_screenRect); - } - } - } else if (item->_updated) { - // add old rect to erase list, new item to draw list - item->calcRects(*this); + // Keep erase rects for this screen item from drawing outside + // of its owner plane + Common::Rect visibleItemScreenRect; + if (visibleItem != nullptr) { + visibleItemScreenRect = visibleItem->_screenRect; + visibleItemScreenRect.clip(_screenRect); + } + + if (item->_deleted) { + // Add item's rect to erase list + if ( + visibleItem != nullptr && + !visibleItemScreenRect.isEmpty() + ) { if (g_sci->_gfxRemap32->getRemapCount()) { - // if item and vitem don't overlap, ... - if (item->_screenRect.isEmpty() || - i >= visiblePlaneItemCount || - vitem == nullptr || - vitem->_screenRect.isEmpty() || - !vitem->_screenRect.intersects(item->_screenRect) - ) { - // add item to draw list, and old rect to erase list - if (!item->_screenRect.isEmpty()) { - drawList.add(item, item->_screenRect); - mergeToRectList(item->_screenRect, eraseList); - } - if ( - i < visiblePlaneItemCount && - vitem != nullptr && - !vitem->_screenRect.isEmpty() - ) { - mergeToRectList(vitem->_screenRect, eraseList); - } - } else { - // otherwise, add bounding box of old+new to erase list, - // and item to draw list + mergeToRectList(visibleItemScreenRect, eraseList); + } else { + eraseList.add(visibleItemScreenRect); + } + } + } - // TODO: This was changed from disasm, verify please! - Common::Rect extendedScreenRect = vitem->_screenRect; - extendedScreenRect.extend(item->_screenRect); + if (!item->_created && !item->_updated) { + continue; + } - drawList.add(item, item->_screenRect); - mergeToRectList(extendedScreenRect, eraseList); - } + item->calcRects(*this); + const Common::Rect itemScreenRect(item->_screenRect); + + if (item->_created) { + // Add item to draw list + if(!itemScreenRect.isEmpty()) { + if (g_sci->_gfxRemap32->getRemapCount()) { + drawList.add(item, itemScreenRect); + mergeToRectList(itemScreenRect, eraseList); } else { - // if no active remaps, just add item to draw list and old rect - // to erase list - if (!item->_screenRect.isEmpty()) { - drawList.add(item, item->_screenRect); + drawList.add(item, itemScreenRect); + } + } + } else { + // Add old rect to erase list, new item to draw list + + if (g_sci->_gfxRemap32->getRemapCount()) { + // If item and visibleItem don't overlap... + if (itemScreenRect.isEmpty() || + visibleItem == nullptr || + visibleItemScreenRect.isEmpty() || + !visibleItemScreenRect.intersects(itemScreenRect) + ) { + // ...add item to draw list, and old rect to erase list... + if (!itemScreenRect.isEmpty()) { + drawList.add(item, itemScreenRect); + mergeToRectList(itemScreenRect, eraseList); } - if ( - i < visiblePlaneItemCount && - vitem != nullptr && - !vitem->_screenRect.isEmpty() - ) { - eraseList.add(vitem->_screenRect); + if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) { + mergeToRectList(visibleItemScreenRect, eraseList); } + } else { + // ...otherwise, add bounding box of old+new to erase list, + // and item to draw list + Common::Rect extendedScreenRect = visibleItemScreenRect; + extendedScreenRect.extend(itemScreenRect); + + drawList.add(item, itemScreenRect); + mergeToRectList(extendedScreenRect, eraseList); + } + } else { + // If no active remaps, just add item to draw list and old rect + // to erase list + + // TODO: SCI3 update rects for VMD? + if (!itemScreenRect.isEmpty()) { + drawList.add(item, itemScreenRect); + } + if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) { + eraseList.add(visibleItemScreenRect); } } } @@ -385,40 +412,44 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList // We store the current size of the drawlist, as we want to loop // over the currently inserted entries later. DrawList::size_type drawListSizePrimary = drawList.size(); + const RectList::size_type eraseListCount = eraseList.size(); - if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"???? + // TODO: Figure out which games need which rendering method + if (/* TODO: dword_C6288 */ false) { // "high resolution pictures" _screenItemList.sort(); - bool encounteredPic = false; - bool v81 = false; + bool pictureDrawn = false; + bool screenItemDrawn = false; - for (RectList::size_type i = 0; i < eraseList.size(); ++i) { - const Common::Rect *rect = eraseList[i]; + for (RectList::size_type i = 0; i < eraseListCount; ++i) { + const Common::Rect &rect = *eraseList[i]; - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { ScreenItem *item = _screenItemList[j]; - if (j < _screenItemList.size() && item != nullptr) { - if (rect->intersects(item->_screenRect)) { - const Common::Rect intersection = rect->findIntersectingRect(item->_screenRect); - if (!item->_deleted) { - if (encounteredPic) { - if (item->_celInfo.type == kCelTypePic) { - if (v81 || item->_celInfo.celNo == 0) { - drawList.add(item, intersection); - } - } else { - if (!item->_updated && !item->_created) { - drawList.add(item, intersection); - } - v81 = true; + if (item == nullptr) { + continue; + } + + if (rect.intersects(item->_screenRect)) { + const Common::Rect intersection = rect.findIntersectingRect(item->_screenRect); + if (!item->_deleted) { + if (pictureDrawn) { + if (item->_celInfo.type == kCelTypePic) { + if (screenItemDrawn || item->_celInfo.celNo == 0) { + mergeToDrawList(j, intersection, drawList); } } else { if (!item->_updated && !item->_created) { - drawList.add(item, intersection); - } - if (item->_celInfo.type == kCelTypePic) { - encounteredPic = true; + mergeToDrawList(j, intersection, drawList); } + screenItemDrawn = true; + } + } else { + if (!item->_updated && !item->_created) { + mergeToDrawList(j, intersection, drawList); + } + if (item->_celInfo.type == kCelTypePic) { + pictureDrawn = true; } } } @@ -428,22 +459,23 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList _screenItemList.unsort(); } else { - // add all items overlapping the erase list to the draw list - for (RectList::size_type i = 0; i < eraseList.size(); ++i) { - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + // Add all items overlapping the erase list to the draw list + for (RectList::size_type i = 0; i < eraseListCount; ++i) { + const Common::Rect &rect = *eraseList[i]; + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { ScreenItem *item = _screenItemList[j]; if ( item != nullptr && !item->_created && !item->_updated && !item->_deleted && - eraseList[i]->intersects(item->_screenRect) + rect.intersects(item->_screenRect) ) { - drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect)); + drawList.add(item, rect.findIntersectingRect(item->_screenRect)); } } } } - if (g_sci->_gfxRemap32->getRemapCount() == 0) { // no remaps active? + if (g_sci->_gfxRemap32->getRemapCount() == 0) { // Add all items that overlap with items in the drawlist and have higher // priority. @@ -451,23 +483,28 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList // those that were added because of the erase list in the previous loop, // or those to be added in this loop. for (DrawList::size_type i = 0; i < drawListSizePrimary; ++i) { - DrawItem *dli = drawList[i]; + const DrawItem *drawListEntry = nullptr; + if (i < drawList.size()) { + drawListEntry = drawList[i]; + } - for (ScreenItemList::size_type j = 0; j < planeItemCount; ++j) { - ScreenItem *sli = _screenItemList[j]; + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + ScreenItem *newItem = nullptr; + if (j < _screenItemList.size()) { + newItem = _screenItemList[j]; + } if ( - i < drawList.size() && dli != nullptr && - j < _screenItemList.size() && sli != nullptr && - !sli->_created && !sli->_updated && !sli->_deleted + drawListEntry != nullptr && newItem != nullptr && + !newItem->_created && !newItem->_updated && !newItem->_deleted ) { - ScreenItem *item = dli->screenItem; + const ScreenItem *drawnItem = drawListEntry->screenItem; if ( - (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) && - dli->rect.intersects(sli->_screenRect) + (newItem->_priority > drawnItem->_priority || (newItem->_priority == drawnItem->_priority && newItem->_object > drawnItem->_object)) && + drawListEntry->rect.intersects(newItem->_screenRect) ) { - drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect)); + mergeToDrawList(j, drawListEntry->rect.findIntersectingRect(newItem->_screenRect), drawList); } } } @@ -475,14 +512,11 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList } decrementScreenItemArrayCounts(&visiblePlane, false); - _screenItemList.pack(); - visiblePlane._screenItemList.pack(); } void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) { - // The size of the screenItemList may change, so it is - // critical to re-check the size on each iteration - for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { ScreenItem *item = _screenItemList[i]; if (item != nullptr) { @@ -495,7 +529,7 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force visiblePlane->_screenItemList.findByObject(item->_object) != nullptr ) ) { - *visiblePlane->_screenItemList[i] = *_screenItemList[i]; + *visiblePlane->_screenItemList[i] = *item; } if (item->_updated) { @@ -514,175 +548,180 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force if (item->_deleted) { item->_deleted--; if (!item->_deleted) { - visiblePlane->_screenItemList.erase_at(i); + if (visiblePlane != nullptr && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr) { + visiblePlane->_screenItemList.erase_at(i); + } _screenItemList.erase_at(i); } } } } + + _screenItemList.pack(); + if (visiblePlane != nullptr) { + visiblePlane->_screenItemList.pack(); + } } -void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const { - if (_type == kPlaneTypeTransparent) { - for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { - const Common::Rect *r = transparentEraseList[i]; - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { - ScreenItem *item = _screenItemList[j]; - if (item != nullptr) { - if (r->intersects(item->_screenRect)) { - mergeToDrawList(j, *r, drawList); - } +void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const { + const RectList::size_type higherEraseCount = higherEraseList.size(); + + if (_type == kPlaneTypeTransparent || _type == kPlaneTypeTransparentPicture) { + for (RectList::size_type i = 0; i < higherEraseCount; ++i) { + const Common::Rect &r = *higherEraseList[i]; + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + const ScreenItem *item = _screenItemList[j]; + if (item != nullptr && r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); } } } } else { - for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { - Common::Rect *r = transparentEraseList[i]; - if (r->intersects(_screenRect)) { - r->clip(_screenRect); - mergeToRectList(*r, eraseList); - - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { - ScreenItem *item = _screenItemList[j]; - - if (item != nullptr) { - if (r->intersects(item->_screenRect)) { - mergeToDrawList(j, *r, drawList); - } + for (RectList::size_type i = 0; i < higherEraseCount; ++i) { + Common::Rect r = *higherEraseList[i]; + if (r.intersects(_screenRect)) { + r.clip(_screenRect); + mergeToRectList(r, eraseList); + + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + const ScreenItem *item = _screenItemList[j]; + if (item != nullptr && r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); } } - Common::Rect ptr[4]; - const Common::Rect *r2 = transparentEraseList[i]; - int count = splitRects(*r2, *r, ptr); - for (int k = count - 1; k >= 0; --k) { - transparentEraseList.add(ptr[k]); + Common::Rect outRects[4]; + const Common::Rect &r2 = *higherEraseList[i]; + int splitCount = splitRects(r2, r, outRects); + while (splitCount--) { + higherEraseList.add(outRects[splitCount]); } - transparentEraseList.erase_at(i); + higherEraseList.erase_at(i); } } - transparentEraseList.pack(); + higherEraseList.pack(); } } -void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const { - for (DrawList::size_type i = 0; i < drawList.size(); ++i) { - const Common::Rect &r = drawList[i]->rect; - - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { - ScreenItem *item = _screenItemList[j]; - if (item != nullptr) { - if (r.intersects(item->_screenRect)) { - mergeToDrawList(j, r, transparentDrawList); - } +void Plane::filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const { + const DrawList::size_type lowerDrawCount = lowerDrawList.size(); + for (DrawList::size_type i = 0; i < lowerDrawCount; ++i) { + const Common::Rect &r = lowerDrawList[i]->rect; + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + const ScreenItem *item = _screenItemList[j]; + if (item != nullptr && r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); } } } } -void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const { - for (RectList::size_type i = 0; i < eraseList.size(); ++i) { - const Common::Rect &r = *eraseList[i]; - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { - ScreenItem *item = _screenItemList[j]; - - if (item != nullptr) { - if (r.intersects(item->_screenRect)) { - mergeToDrawList(j, r, drawList); - } +void Plane::filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const { + const RectList::size_type lowerEraseCount = lowerEraseList.size(); + for (RectList::size_type i = 0; i < lowerEraseCount; ++i) { + const Common::Rect &r = *lowerEraseList[i]; + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + const ScreenItem *item = _screenItemList[j]; + if (item != nullptr && r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); } } } } void Plane::mergeToDrawList(const ScreenItemList::size_type index, const Common::Rect &rect, DrawList &drawList) const { - RectList rects; - - ScreenItem *item = _screenItemList[index]; - Common::Rect r = item->_screenRect; + RectList mergeList; + ScreenItem &item = *_screenItemList[index]; + Common::Rect r = item._screenRect; r.clip(rect); - rects.add(r); + mergeList.add(r); - for (RectList::size_type i = 0; i < rects.size(); ++i) { - r = *rects[i]; + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + r = *mergeList[i]; - for (DrawList::size_type j = 0; j < drawList.size(); ++j) { - const DrawItem *drawitem = drawList[j]; - if (item->_object == drawitem->screenItem->_object) { - if (drawitem->rect.contains(r)) { - rects.erase_at(i); + const DrawList::size_type drawCount = drawList.size(); + for (DrawList::size_type j = 0; j < drawCount; ++j) { + const DrawItem &drawItem = *drawList[j]; + if (item._object == drawItem.screenItem->_object) { + if (drawItem.rect.contains(r)) { + mergeList.erase_at(i); break; } Common::Rect outRects[4]; - const int count = splitRects(r, drawitem->rect, outRects); - if (count != -1) { - for (int k = count - 1; k >= 0; --k) { - rects.add(outRects[k]); + int splitCount = splitRects(r, drawItem.rect, outRects); + if (splitCount != -1) { + while (splitCount--) { + mergeList.add(outRects[splitCount]); } - rects.erase_at(i); + mergeList.erase_at(i); // proceed to the next rect - r = *rects[++i]; + r = *mergeList[++i]; } } } } - rects.pack(); + mergeList.pack(); - for (RectList::size_type i = 0; i < rects.size(); ++i) { - drawList.add(item, *rects[i]); + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + drawList.add(&item, *mergeList[i]); } } -void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const { - RectList temp; - temp.add(rect); +void Plane::mergeToRectList(const Common::Rect &rect, RectList &eraseList) const { + RectList mergeList; + Common::Rect r; + mergeList.add(rect); - for (RectList::size_type i = 0; i < temp.size(); ++i) { - Common::Rect r = *temp[i]; + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + r = *mergeList[i]; - for (RectList::size_type j = 0; j < rectList.size(); ++j) { - const Common::Rect *innerRect = rectList[j]; - if (innerRect->contains(r)) { - temp.erase_at(i); + const RectList::size_type eraseCount = eraseList.size(); + for (RectList::size_type j = 0; j < eraseCount; ++j) { + const Common::Rect &eraseRect = *eraseList[j]; + if (eraseRect.contains(r)) { + mergeList.erase_at(i); break; } - Common::Rect out[4]; - const int count = splitRects(r, *innerRect, out); - if (count != -1) { - for (int k = count - 1; k >= 0; --k) { - temp.add(out[k]); + Common::Rect outRects[4]; + int splitCount = splitRects(r, eraseRect, outRects); + if (splitCount != -1) { + while (splitCount--) { + mergeList.add(outRects[splitCount]); } - temp.erase_at(i); + mergeList.erase_at(i); // proceed to the next rect - r = *temp[++i]; + r = *mergeList[++i]; } } } - temp.pack(); + mergeList.pack(); - for (RectList::size_type i = 0; i < temp.size(); ++i) { - rectList.add(*temp[i]); + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + eraseList.add(*mergeList[i]); } } void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) { - for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) { - if (*screenItemPtr != nullptr) { - ScreenItem &screenItem = **screenItemPtr; - if (!screenItem._deleted) { - screenItem.calcRects(*this); - if (!screenItem._screenRect.isEmpty()) { - drawList.add(&screenItem, screenItem._screenRect); - } + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { + ScreenItem *screenItem = _screenItemList[i]; + if (screenItem != nullptr && !screenItem->_deleted) { + screenItem->calcRects(*this); + if (!screenItem->_screenRect.isEmpty()) { + mergeToDrawList(i, screenItem->_screenRect, drawList); } } } @@ -696,21 +735,27 @@ void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList breakDrawListByPlanes(drawList, planeList); --_redrawAllCount; decrementScreenItemArrayCounts(visiblePlane, true); - _screenItemList.pack(); - if (visiblePlane != nullptr) { - visiblePlane->_screenItemList.pack(); - } } void Plane::setType() { - if (_pictureId == kPlanePicOpaque) { - _type = kPlaneTypeOpaque; - } else if (_pictureId == kPlanePicTransparent) { - _type = kPlaneTypeTransparent; - } else if (_pictureId == kPlanePicColored) { + switch (_pictureId) { + case kPlanePicColored: _type = kPlaneTypeColored; - } else { - _type = kPlaneTypePicture; + break; + case kPlanePicTransparent: + _type = kPlaneTypeTransparent; + break; + case kPlanePicOpaque: + _type = kPlaneTypeOpaque; + break; + case kPlanePicTransparentPicture: + _type = kPlaneTypeTransparentPicture; + break; + default: + if (_type != kPlaneTypeTransparentPicture) { + _type = kPlaneTypePicture; + } + break; } } @@ -731,10 +776,12 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) { _planeRect.right > other->_planeRect.right || _planeRect.bottom > other->_planeRect.bottom ) { + // the plane moved or got larger _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); - _updated = g_sci->_gfxFrameout->getScreenCount(); + _moved = g_sci->_gfxFrameout->getScreenCount(); } else if (_planeRect != other->_planeRect) { - _updated = g_sci->_gfxFrameout->getScreenCount(); + // the plane got smaller + _moved = g_sci->_gfxFrameout->getScreenCount(); } if (_priority != other->_priority) { @@ -755,7 +802,7 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) { _deleted = 0; if (_created == 0) { - _moved = g_sci->_gfxFrameout->getScreenCount(); + _updated = g_sci->_gfxFrameout->getScreenCount(); } convertGameRectToPlaneRect(); @@ -801,18 +848,22 @@ void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool } void Plane::remapMarkRedraw() { - for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) { - if (*screenItemPtr != nullptr) { - ScreenItem &screenItem = **screenItemPtr; - if (screenItem.getCelObj()._remap && !screenItem._deleted && !screenItem._created) { - screenItem._updated = g_sci->_gfxFrameout->getScreenCount(); - } + ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { + ScreenItem *screenItem = _screenItemList[i]; + if ( + screenItem != nullptr && + !screenItem->_deleted && !screenItem->_created && + screenItem->getCelObj()._remap + ) { + screenItem->_updated = g_sci->_gfxFrameout->getScreenCount(); } } } #pragma mark - #pragma mark PlaneList + void PlaneList::add(Plane *plane) { for (iterator it = begin(); it != end(); ++it) { if ((*it)->_priority > plane->_priority) { diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h index c93fb5b64e..acd535e75a 100644 --- a/engines/sci/graphics/plane32.h +++ b/engines/sci/graphics/plane32.h @@ -32,19 +32,21 @@ namespace Sci { enum PlaneType { - kPlaneTypeColored = 0, - kPlaneTypePicture = 1, - kPlaneTypeTransparent = 2, - kPlaneTypeOpaque = 3 + kPlaneTypeColored = 0, + kPlaneTypePicture = 1, + kPlaneTypeTransparent = 2, + kPlaneTypeOpaque = 3, + kPlaneTypeTransparentPicture = 4 }; enum PlanePictureCodes { - // NOTE: Any value at or below 65532 means the plane + // NOTE: Any value at or below 65531 means the plane // is a kPlaneTypePicture. - kPlanePic = 65532, - kPlanePicOpaque = 65533, - kPlanePicTransparent = 65534, - kPlanePicColored = 65535 + kPlanePic = 65531, + kPlanePicTransparentPicture = 65532, + kPlanePicOpaque = 65533, + kPlanePicTransparent = 65534, + kPlanePicColored = 65535 }; #pragma mark - @@ -62,7 +64,14 @@ public: #pragma mark DrawList struct DrawItem { + /** + * The screen item to draw. + */ ScreenItem *screenItem; + + /** + * The target rectangle of the draw operation. + */ Common::Rect rect; inline bool operator<(const DrawItem &other) const { @@ -189,16 +198,15 @@ public: * not match * - `deleted` is set when the plane is deleted by a * kernel call - * - `moved` is set when the plane is synchronised from - * another plane and is not already in the "created" - * state + * - `moved` is set when the plane has been moved or + * resized */ int _created, _updated, _deleted, _moved; /** * The vanishing point for the plane. Used when - * calculating the correct scaling of the plane's screen - * items according to their position. + * automatically calculating the correct scaling of the + * plane's screen items according to their position. */ Common::Point _vanishingPoint; @@ -358,42 +366,33 @@ public: private: /** * Splits all rects in the given draw list at the edges - * of all non-transparent planes above the current - * plane. + * of all higher-priority, non-transparent, intersecting + * planes. */ void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const; /** - * Splits all rects in the given erase list rects at the - * edges of all non-transparent planes above the current - * plane. + * Splits all rects in the given erase list at the + * edges of higher-priority, non-transparent, + * intersecting planes. */ void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const; /** - * Synchronises changes to screen items from the current - * plane to the visible plane and deletes screen items - * from the current plane that have been marked as - * deleted. If `forceUpdate` is true, all screen items - * on the visible plane will be updated, even if they - * are not marked as having changed. - */ - void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate); - - /** - * Merges the screen item from this plane at the given - * index into the given draw list, clipped to the given - * rect. TODO: Finish documenting + * Adds the screen item at `index` into `drawList`, + * ensuring it is only drawn within the bounds of + * `rect`. If an existing draw list entry exists + * for this screen item, it will be modified. + * Otherwise, a new entry will be added. */ void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const; /** - * Adds the given rect into the given rect list, - * merging it with other rects already inside the list, - * if possible, to avoid overdraw. TODO: Finish - * documenting + * Merges `rect` with an existing rect in `eraseList`, + * if possible. Otherwise, adds the rect as a new entry + * to `eraseList`. */ - void mergeToRectList(const Common::Rect &rect, RectList &rectList) const; + void mergeToRectList(const Common::Rect &rect, RectList &eraseList) const; public: /** @@ -406,19 +405,73 @@ public: void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList); /** - * TODO: Documentation + * Synchronises changes to screen items from the current + * plane to the visible plane and deletes screen items + * from the current plane that have been marked as + * deleted. If `forceUpdate` is true, all screen items + * on the visible plane will be updated, even if they + * are not marked as having changed. + */ + void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate); + + /** + * This method is called from the highest priority plane + * to the lowest priority plane. + * + * Adds screen items from this plane to the draw list + * that must be redrawn because they intersect entries + * in the `higherEraseList`. + * + * If this plane is opaque, all intersecting erase rects + * in `lowerEraseList` are removed, as they would be + * completely overwritten by the contents of this plane. + * + * If this plane is transparent, erase rects from the + * `lowerEraseList` are added to the erase list for this + * plane, so that lower planes. + * + * @param drawList The draw list for this plane. + * @param eraseList The erase list for this plane. + * @param higherEraseList The erase list for a plane + * above this plane. */ - void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const; + void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const; /** - * TODO: Documentation + * This method is called from the lowest priority plane + * to the highest priority plane. + * + * Adds screen items from this plane to the draw list + * that must be drawn because the lower plane is being + * redrawn and potentially transparent screen items + * from this plane would draw over the lower priority + * plane's screen items. + * + * This method applies only to transparent planes. + * + * @param drawList The draw list for this plane. + * @param eraseList The erase list for a plane below + * this plane. */ - void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const; + void filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const; /** - * TODO: Documentation + * This method is called from the lowest priority plane + * to the highest priority plane. + * + * Adds screen items from this plane to the draw list + * that must be drawn because the lower plane is being + * redrawn and potentially transparent screen items + * from this plane would draw over the lower priority + * plane's screen items. + * + * This method applies only to transparent planes. + * + * @param drawList The draw list for this plane. + * @param lowerDrawList The draw list for a plane below + * this plane. */ - void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const; + void filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const; /** * Updates all of the plane's non-deleted screen items @@ -442,6 +495,8 @@ private: using PlaneListBase::push_back; public: + typedef int size_type; + // A method for finding the index of a plane inside a // PlaneList is used because entries in the main plane // list and visible plane list of GfxFrameout are diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp index ff49e52f13..2abf03ea29 100644 --- a/engines/sci/graphics/remap.cpp +++ b/engines/sci/graphics/remap.cpp @@ -21,31 +21,23 @@ */ #include "sci/sci.h" -#include "sci/resource.h" #include "sci/graphics/palette.h" -#include "sci/graphics/palette32.h" #include "sci/graphics/remap.h" #include "sci/graphics/screen.h" namespace Sci { -#pragma mark - -#pragma mark SCI16 remapping (QFG4 demo) - GfxRemap::GfxRemap(GfxPalette *palette) : _palette(palette) { _remapOn = false; resetRemapping(); } -GfxRemap::~GfxRemap() { -} - byte GfxRemap::remapColor(byte remappedColor, byte screenColor) { assert(_remapOn); - if (_remappingType[remappedColor] == kRemappingByRange) + if (_remappingType[remappedColor] == kRemapByRange) return _remappingByRange[screenColor]; - else if (_remappingType[remappedColor] == kRemappingByPercent) + else if (_remappingType[remappedColor] == kRemapByPercent) return _remappingByPercent[screenColor]; else error("remapColor(): Color %d isn't remapped", remappedColor); @@ -58,7 +50,7 @@ void GfxRemap::resetRemapping() { _remappingPercentToSet = 0; for (int i = 0; i < 256; i++) { - _remappingType[i] = kRemappingNone; + _remappingType[i] = kRemapNone; _remappingByPercent[i] = i; _remappingByRange[i] = i; } @@ -80,7 +72,7 @@ void GfxRemap::setRemappingPercent(byte color, byte percent) { _remappingByPercent[i] = _palette->kernelFindColor(r, g, b); } - _remappingType[color] = kRemappingByPercent; + _remappingType[color] = kRemapByPercent; } void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) { @@ -90,7 +82,7 @@ void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) { _remappingByRange[i] = i + base; } - _remappingType[color] = kRemappingByRange; + _remappingType[color] = kRemapByRange; } void GfxRemap::updateRemapping() { @@ -104,277 +96,4 @@ void GfxRemap::updateRemapping() { } } } - -#pragma mark - -#pragma mark SCI32 remapping - -#ifdef ENABLE_SCI32 - -GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) { - for (int i = 0; i < REMAP_COLOR_COUNT; i++) - _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); - _noMapStart = _noMapCount = 0; - _update = false; - _remapCount = 0; - - // The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle. - // All versions of KQ7 are using the older remap range semantics. - _remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) ? 244 : 254; -} - -void GfxRemap32::remapOff(byte color) { - if (!color) { - for (int i = 0; i < REMAP_COLOR_COUNT; i++) - _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); - - _remapCount = 0; - } else { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - _remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); - _remapCount--; - } - - _update = true; -} - -void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemappingByRange); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} - -void GfxRemap32::setRemappingPercent(byte color, byte percent) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemappingByPercent); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} - -void GfxRemap32::setRemappingToGray(byte color, byte gray) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemappingToGray); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} - -void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemappingToPercentGray); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} - -void GfxRemap32::setNoMatchRange(byte from, byte count) { - _noMapStart = from; - _noMapCount = count; -} - -bool GfxRemap32::remapEnabled(byte color) const { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - return (_remaps[index].type != kRemappingNone); -} - -byte GfxRemap32::remapColor(byte color, byte target) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - if (_remaps[index].type != kRemappingNone) - return _remaps[index].remap[target]; - else - return target; -} - -void GfxRemap32::initColorArrays(byte index) { - Palette *curPalette = &_palette->_sysPalette; - RemapParams *curRemap = &_remaps[index]; - - memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); - memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); -} - -bool GfxRemap32::updateRemap(byte index, bool palChanged) { - int result; - RemapParams *curRemap = &_remaps[index]; - const Palette *curPalette = &_palette->_sysPalette; - const Palette *nextPalette = _palette->getNextPalette(); - bool changed = false; - - if (!_update && !palChanged) - return false; - - Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false); - - switch (curRemap->type) { - case kRemappingNone: - return false; - case kRemappingByRange: - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) { - if (curRemap->from <= i && i <= curRemap->to) - result = i + curRemap->base; - else - result = i; - - if (curRemap->remap[i] != result) { - changed = true; - curRemap->remap[i] = result; - } - - curRemap->colorChanged[i] = true; - } - return changed; - case kRemappingByPercent: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - // NOTE: This method uses nextPalette instead of curPalette - Color color = nextPalette->colors[i]; - - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } - - if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i]) { - byte red = CLIP<byte>(color.r * curRemap->percent / 100, 0, 255); - byte green = CLIP<byte>(color.g * curRemap->percent / 100, 0, 255); - byte blue = CLIP<byte>(color.b * curRemap->percent / 100, 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } - } - } - - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldPercent = curRemap->percent; - return changed; - case kRemappingToGray: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color color = curPalette->colors[i]; - - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } - - if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) { - byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; - byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); - byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); - byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } - } - } - - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldGray = curRemap->gray; - return changed; - case kRemappingToPercentGray: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color color = curPalette->colors[i]; - - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } - - if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) { - byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; - lumosity = lumosity * curRemap->percent / 100; - byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); - byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); - byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } - } - } - - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldPercent = curRemap->percent; - curRemap->oldGray = curRemap->gray; - return changed; - default: - return false; - } -} - -static int colorDistance(Color a, Color b) { - int rDiff = (a.r - b.r) * (a.r - b.r); - int gDiff = (a.g - b.g) * (a.g - b.g); - int bDiff = (a.b - b.b) * (a.b - b.b); - return rDiff + gDiff + bDiff; -} - -bool GfxRemap32::applyRemap(byte index) { - RemapParams *curRemap = &_remaps[index]; - const bool *cycleMap = _palette->getCyclemap(); - bool unmappedColors[NON_REMAPPED_COLOR_COUNT]; - bool changed = false; - - Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false); - if (_noMapCount) - Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true); - - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) { - if (cycleMap[i]) - unmappedColors[i] = true; - } - - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color targetColor = curRemap->targetColor[i]; - bool colorChanged = curRemap->colorChanged[curRemap->remap[i]]; - - if (!_targetChanged[i] && !colorChanged) - continue; - - if (_targetChanged[i] && colorChanged) - if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i]) - continue; - - int diff = 0; - int16 result = _palette->matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors); - if (result != -1 && curRemap->remap[i] != result) { - changed = true; - curRemap->remap[i] = result; - curRemap->distance[i] = diff; - } - } - - return changed; -} - -bool GfxRemap32::remapAllTables(bool palChanged) { - bool changed = false; - - for (int i = 0; i < REMAP_COLOR_COUNT; i++) { - changed |= updateRemap(i, palChanged); - } - - _update = false; - return changed; -} - -#endif - } // End of namespace Sci diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h index d012568f7f..98177f6d19 100644 --- a/engines/sci/graphics/remap.h +++ b/engines/sci/graphics/remap.h @@ -24,42 +24,36 @@ #define SCI_GRAPHICS_REMAP_H #include "common/array.h" -#include "sci/graphics/helpers.h" +#include "common/serializer.h" namespace Sci { class GfxScreen; -enum ColorRemappingType { - kRemappingNone = 0, - kRemappingByRange = 1, - kRemappingByPercent = 2, - kRemappingToGray = 3, - kRemappingToPercentGray = 4 -}; - -#define REMAP_COLOR_COUNT 9 -#define NON_REMAPPED_COLOR_COUNT 236 - /** - * Remap class, handles color remapping + * This class handles color remapping for the QFG4 demo. */ class GfxRemap { +private: + enum ColorRemappingType { + kRemapNone = 0, + kRemapByRange = 1, + kRemapByPercent = 2 + }; + public: GfxRemap(GfxPalette *_palette); - ~GfxRemap(); void resetRemapping(); void setRemappingPercent(byte color, byte percent); void setRemappingRange(byte color, byte from, byte to, byte base); bool isRemapped(byte color) const { - return _remapOn && (_remappingType[color] != kRemappingNone); + return _remapOn && (_remappingType[color] != kRemapNone); } byte remapColor(byte remappedColor, byte screenColor); void updateRemapping(); private: - GfxScreen *_screen; GfxPalette *_palette; bool _remapOn; @@ -68,87 +62,6 @@ private: byte _remappingByRange[256]; uint16 _remappingPercentToSet; }; - -#ifdef ENABLE_SCI32 - -struct RemapParams { - byte from; - byte to; - byte base; - byte gray; - byte oldGray; - byte percent; - byte oldPercent; - ColorRemappingType type; - Color curColor[256]; - Color targetColor[256]; - byte distance[256]; - byte remap[256]; - bool colorChanged[256]; - - RemapParams() { - from = to = base = gray = oldGray = percent = oldPercent = 0; - type = kRemappingNone; - - // curColor and targetColor are initialized in GfxRemap32::initColorArrays - memset(curColor, 0, 256 * sizeof(Color)); - memset(targetColor, 0, 256 * sizeof(Color)); - memset(distance, 0, 256); - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) - remap[i] = i; - Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); - } - - RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, ColorRemappingType type_) { - from = from_; - to = to_; - base = base_; - gray = oldGray = gray_; - percent = oldPercent = percent_; - type = type_; - - // curColor and targetColor are initialized in GfxRemap32::initColorArrays - memset(curColor, 0, 256 * sizeof(Color)); - memset(targetColor, 0, 256 * sizeof(Color)); - memset(distance, 0, 256); - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) - remap[i] = i; - Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); - } -}; - -class GfxRemap32 { -public: - GfxRemap32(GfxPalette32 *palette); - ~GfxRemap32() {} - - void remapOff(byte color); - void setRemappingRange(byte color, byte from, byte to, byte base); - void setRemappingPercent(byte color, byte percent); - void setRemappingToGray(byte color, byte gray); - void setRemappingToPercentGray(byte color, byte gray, byte percent); - void setNoMatchRange(byte from, byte count); - bool remapEnabled(byte color) const; - byte remapColor(byte color, byte target); - bool remapAllTables(bool palChanged); - int getRemapCount() const { return _remapCount; } - int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; } - int getEndColor() const { return _remapEndColor; } -private: - GfxPalette32 *_palette; - RemapParams _remaps[REMAP_COLOR_COUNT]; - bool _update; - byte _noMapStart, _noMapCount; - bool _targetChanged[NON_REMAPPED_COLOR_COUNT]; - byte _remapEndColor; - int _remapCount; - - void initColorArrays(byte index); - bool applyRemap(byte index); - bool updateRemap(byte index, bool palChanged); -}; -#endif - } // End of namespace Sci #endif diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp new file mode 100644 index 0000000000..d5a2362f14 --- /dev/null +++ b/engines/sci/graphics/remap32.cpp @@ -0,0 +1,468 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "sci/sci.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h" + +namespace Sci { + +#pragma mark SingleRemap + +void SingleRemap::reset() { + _lastPercent = 100; + _lastGray = 0; + + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 0; i < remapStartColor; ++i) { + const Color &color = currentPalette.colors[i]; + _remapColors[i] = i; + _originalColors[i] = color; + _originalColorsChanged[i] = true; + _idealColors[i] = color; + _idealColorsChanged[i] = false; + _matchDistances[i] = 0; + } +} + +bool SingleRemap::update() { + switch (_type) { + case kRemapNone: + break; + case kRemapByRange: + return updateRange(); + case kRemapByPercent: + return updateBrightness(); + case kRemapToGray: + return updateSaturation(); + case kRemapToPercentGray: + return updateSaturationAndBrightness(); + default: + error("Illegal remap type %d", _type); + } + + return false; +} + +bool SingleRemap::updateRange() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + bool updated = false; + + for (uint i = 0; i < remapStartColor; ++i) { + uint8 targetColor; + if (_from <= i && i <= _to) { + targetColor = i + _delta; + } else { + targetColor = i; + } + + if (_remapColors[i] != targetColor) { + updated = true; + _remapColors[i] = targetColor; + } + + _originalColorsChanged[i] = true; + } + + return updated; +} + +bool SingleRemap::updateBrightness() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); + for (uint i = 1; i < remapStartColor; ++i) { + Color color(nextPalette.colors[i]); + + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; + } + + if (_percent != _lastPercent || _originalColorsChanged[i]) { + // NOTE: SSCI checked if percent was over 100 and only + // then clipped values, but we always unconditionally + // ensure the result is in the correct range + color.r = MIN(255, (uint16)color.r * _percent / 100); + color.g = MIN(255, (uint16)color.g * _percent / 100); + color.b = MIN(255, (uint16)color.b * _percent / 100); + + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; + } + } + } + + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastPercent = _percent; + return updated; +} + +bool SingleRemap::updateSaturation() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 1; i < remapStartColor; ++i) { + Color color(currentPalette.colors[i]); + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; + } + + if (_gray != _lastGray || _originalColorsChanged[i]) { + const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; + + color.r = MIN(255, color.r - ((color.r - luminosity) * _gray / 100)); + color.g = MIN(255, color.g - ((color.g - luminosity) * _gray / 100)); + color.b = MIN(255, color.b - ((color.b - luminosity) * _gray / 100)); + + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; + } + } + } + + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastGray = _gray; + return updated; +} + +bool SingleRemap::updateSaturationAndBrightness() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 1; i < remapStartColor; i++) { + Color color(currentPalette.colors[i]); + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; + } + + if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) { + const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; + + color.r = MIN(255, color.r - ((color.r - luminosity) * _gray) / 100); + color.g = MIN(255, color.g - ((color.g - luminosity) * _gray) / 100); + color.b = MIN(255, color.b - ((color.b - luminosity) * _gray) / 100); + + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; + } + } + } + + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastPercent = _percent; + _lastGray = _gray; + return updated; +} + +bool SingleRemap::apply() { + const GfxRemap32 *const gfxRemap32 = g_sci->_gfxRemap32; + const uint8 remapStartColor = gfxRemap32->getStartColor(); + + // Blocked colors are not allowed to be used as target + // colors for the remap + bool blockedColors[236]; + Common::fill(blockedColors, blockedColors + remapStartColor, false); + + const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap(); + + const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount(); + if (blockedRangeCount) { + const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart(); + Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true); + } + + for (uint i = 0; i < remapStartColor; ++i) { + if (paletteCycleMap[i]) { + blockedColors[i] = true; + } + } + + // NOTE: SSCI did a loop over colors here to create a + // new array of updated, unblocked colors, but then + // never used it + + bool updated = false; + for (uint i = 1; i < remapStartColor; ++i) { + int distance; + + if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) { + continue; + } + + if ( + _idealColorsChanged[i] && + _originalColorsChanged[_remapColors[i]] && + _matchDistances[i] < 100 && + colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i] + ) { + continue; + } + + const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors); + + if (bestColor != -1 && _remapColors[i] != bestColor) { + updated = true; + _remapColors[i] = bestColor; + _matchDistances[i] = distance; + } + } + + return updated; +} + +int SingleRemap::colorDistance(const Color &a, const Color &b) const { + int channelDistance = a.r - b.r; + int distance = channelDistance * channelDistance; + channelDistance = a.g - b.g; + distance += channelDistance * channelDistance; + channelDistance = a.b - b.b; + distance += channelDistance * channelDistance; + return distance; +} + +int16 SingleRemap::matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const { + int16 bestIndex = -1; + int bestDistance = 0xFFFFF; + int distance = minimumDistance; + const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); + + for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) { + if (blockedIndexes[i]) { + continue; + } + + distance = nextPalette.colors[i].r - color.r; + distance *= distance; + if (bestDistance <= distance) { + continue; + } + channelDistance = nextPalette.colors[i].g - color.g; + distance += channelDistance * channelDistance; + if (bestDistance <= distance) { + continue; + } + channelDistance = nextPalette.colors[i].b - color.b; + distance += channelDistance * channelDistance; + if (bestDistance <= distance) { + continue; + } + bestDistance = distance; + bestIndex = i; + } + + // This value is only valid if the last index to + // perform a distance calculation was the best index + outDistance = distance; + return bestIndex; +} + +#pragma mark - +#pragma mark GfxRemap32 + +GfxRemap32::GfxRemap32() : + _needsUpdate(false), + _blockedRangeStart(0), + _blockedRangeCount(0), + _remapStartColor(236), + _numActiveRemaps(0) { + // The `_remapStartColor` seems to always be 236 in SSCI, + // but if it is ever changed then the various C-style + // member arrays hard-coded to 236 need to be changed to + // match the highest possible value of `_remapStartColor` + assert(_remapStartColor == 236); + + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) { + _remaps.resize(9); + } else { + _remaps.resize(19); + } + + _remapEndColor = _remapStartColor + _remaps.size() - 1; +} + +void GfxRemap32::remapOff(const uint8 color) { + if (color == 0) { + remapAllOff(); + return; + } + + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapOff: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + singleRemap._type = kRemapNone; + --_numActiveRemaps; + _needsUpdate = true; +} + +void GfxRemap32::remapAllOff() { + for (uint i = 0, len = _remaps.size(); i < len; ++i) { + _remaps[i]._type = kRemapNone; + } + + _numActiveRemaps = 0; + _needsUpdate = true; +} + +void GfxRemap32::remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapByRange: %d out of remap range", color); + return; + } + + if (from < 0) { + warning("GfxRemap32::remapByRange: attempt to remap negative color %d", from); + return; + } + + if (to >= _remapStartColor) { + warning("GfxRemap32::remapByRange: attempt to remap into the remap zone at %d", to); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._from = from; + singleRemap._to = to; + singleRemap._delta = delta; + singleRemap._type = kRemapByRange; + _needsUpdate = true; +} + +void GfxRemap32::remapByPercent(const uint8 color, const int16 percent) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapByPercent: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._percent = percent; + singleRemap._type = kRemapByPercent; + _needsUpdate = true; +} + +void GfxRemap32::remapToGray(const uint8 color, const int8 gray) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapToGray: %d out of remap range", color); + return; + } + + if (gray < 0 || gray > 100) { + error("RemapToGray percent out of range; gray = %d", gray); + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._gray = gray; + singleRemap._type = kRemapToGray; + _needsUpdate = true; +} + +void GfxRemap32::remapToPercentGray(const uint8 color, const int16 gray, const int16 percent) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapToPercentGray: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._percent = percent; + singleRemap._gray = gray; + singleRemap._type = kRemapToPercentGray; + _needsUpdate = true; +} + +void GfxRemap32::blockRange(const uint8 from, const int16 count) { + _blockedRangeStart = from; + _blockedRangeCount = count; +} + +bool GfxRemap32::remapAllTables(const bool paletteUpdated) { + if (!_needsUpdate && !paletteUpdated) { + return false; + } + + bool updated = false; + + for (SingleRemapsList::iterator it = _remaps.begin(); it != _remaps.end(); ++it) { + if (it->_type != kRemapNone) { + updated |= it->update(); + } + } + + _needsUpdate = false; + return updated; +} +} // End of namespace Sci diff --git a/engines/sci/graphics/remap32.h b/engines/sci/graphics/remap32.h new file mode 100644 index 0000000000..5f629d733e --- /dev/null +++ b/engines/sci/graphics/remap32.h @@ -0,0 +1,400 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_REMAP32_H +#define SCI_GRAPHICS_REMAP32_H + +#include "common/algorithm.h" +#include "common/array.h" +#include "common/scummsys.h" +#include "sci/graphics/helpers.h" + +namespace Sci { +class GfxPalette32; + +enum RemapType { + kRemapNone = 0, + kRemapByRange = 1, + kRemapByPercent = 2, + kRemapToGray = 3, + kRemapToPercentGray = 4 +}; + +#pragma mark - +#pragma mark SingleRemap + +/** + * SingleRemap objects each manage one remapping operation. + */ +class SingleRemap { +public: + SingleRemap() : _type(kRemapNone) {} + + /** + * The type of remap. + */ + RemapType _type; + + /** + * The first color that should be shifted by a range + * remap. + */ + uint8 _from; + + /** + * The last color that should be shifted a range remap. + */ + uint8 _to; + + /** + * The direction and amount that the colors should be + * shifted in a range remap. + */ + int16 _delta; + + /** + * The difference in brightness that should be + * applied by a brightness (percent) remap. + * + * This value may be be greater than 100, in + * which case the color will be oversaturated. + */ + int16 _percent; + + /** + * The amount of desaturation that should be + * applied by a saturation (gray) remap, where + * 0 is full saturation and 100 is full + * desaturation. + */ + uint8 _gray; + + /** + * The final array used by CelObj renderers to composite + * remapped pixels to the screen buffer. + * + * Here is how it works: + * + * The source bitmap being rendered will have pixels + * within the remap range (236-245 or 236-254), and the + * target buffer will have colors in the non-remapped + * range (0-235). + * + * To arrive at the correct color, first the source + * pixel is used to look up the correct SingleRemap for + * that pixel. Then, the final composited color is + * looked up in this array using the target's pixel + * color. In other words, + * `target = _remaps[remapEndColor - source].remapColors[target]`. + */ + uint8 _remapColors[236]; + + /** + * Resets this SingleRemap's color information to + * default values. + */ + void reset(); + + /** + * Recalculates and reapplies remap colors to the + * `_remapColors` array. + */ + bool update(); + +private: + /** + * The previous brightness value. Used to + * determine whether or not targetColors needs + * to be updated. + */ + int16 _lastPercent; + + /** + * The previous saturation value. Used to + * determine whether or not targetColors needs + * to be updated. + */ + uint8 _lastGray; + + /** + * The colors from the current GfxPalette32 palette + * before this SingleRemap is applied. + */ + Color _originalColors[236]; + + /** + * Map of colors that changed in `_originalColors` + * when this SingleRemap was updated. This map is + * transient and gets reset to `false` after the + * SingleRemap finishes updating. + */ + bool _originalColorsChanged[236]; + + /** + * The ideal target RGB color values for each generated + * remap color. + */ + Color _idealColors[236]; + + /** + * Map of colors that changed in `_idealColors` when + * this SingleRemap was updated. This map is transient + * and gets reset to `false` after the SingleRemap + * finishes applying. + */ + bool _idealColorsChanged[236]; + + /** + * When applying a SingleRemap, finding an appropriate + * color in the palette is the responsibility of a + * distance function. Once a match is found, the + * distance of that match is stored here so that the + * next time the SingleRemap is applied, it can check + * the distance from the previous application and avoid + * triggering an expensive redraw of the entire screen + * if the new palette value only changed slightly. + */ + int _matchDistances[236]; + + /** + * Computes the final target values for a range remap + * and applies them directly to the `_remaps` map. + * + * @note Was ByRange in SSCI. + */ + bool updateRange(); + + /** + * Computes the intermediate target values for a + * brightness remap and applies them indirectly via + * the `apply` method. + * + * @note Was ByPercent in SSCI. + */ + bool updateBrightness(); + + /** + * Computes the intermediate target values for a + * saturation remap and applies them indirectly via + * the `apply` method. + * + * @note Was ToGray in SSCI. + */ + bool updateSaturation(); + + /** + * Computes the intermediate target values for a + * saturation + brightness bitmap and applies them + * indirectly via the `apply` method. + * + * @note Was ToPercentGray in SSCI. + */ + bool updateSaturationAndBrightness(); + + /** + * Computes and applies the final values to the + * `_remaps` map. + * + * @note In SSCI, a boolean array of changed values + * was passed into this method, but this was done by + * creating arrays on the stack in the caller. Instead + * of doing this, we simply add another member property + * `_idealColorsChanged` and use that instead. + */ + bool apply(); + + /** + * Calculates the square distance of two colors. + * + * @note In SSCI this method is Rgb24::Dist, but it is + * only used by SingleRemap. + */ + int colorDistance(const Color &a, const Color &b) const; + + /** + * Finds the closest index in the next palette matching + * the given RGB color. Returns -1 if no match can be + * found that is closer than `minimumDistance`. + * + * @note In SSCI, this method is SOLPalette::Match, but + * this particular signature is only used by + * SingleRemap. + */ + int16 matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const; +}; + +#pragma mark - +#pragma mark GfxRemap32 + +/** + * This class provides color remapping support for SCI32 + * games. + */ +class GfxRemap32 : public Common::Serializable { +public: + GfxRemap32(); + + void saveLoadWithSerializer(Common::Serializer &s); + + inline uint8 getRemapCount() const { return _numActiveRemaps; } + inline uint8 getStartColor() const { return _remapStartColor; } + inline uint8 getEndColor() const { return _remapEndColor; } + inline uint8 getBlockedRangeStart() const { return _blockedRangeStart; } + inline int16 getBlockedRangeCount() const { return _blockedRangeCount; } + + /** + * Turns off remapping of the given color. If `color` is + * 0, all remaps are turned off. + */ + void remapOff(const uint8 color); + + /** + * Turns off all color remaps. + */ + void remapAllOff(); + + /** + * Configures a SingleRemap for the remap color `color`. + * The SingleRemap will shift palette colors between + * `from` and `to` (inclusive) by `delta` palette + * entries when the remap is applied. + */ + void remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the brightness of remapped colors by + * `percent`. + */ + void remapByPercent(const uint8 color, const int16 percent); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the saturation of remapped colors by + * `gray`. + */ + void remapToGray(const uint8 color, const int8 gray); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the brightness of remapped colors by + * `percent`, and saturation of remapped colors by + * `gray`. + */ + void remapToPercentGray(const uint8 color, const int16 gray, const int16 percent); + + /** + * Prevents GfxRemap32 from using the given range of + * palette entries as potential remap targets. + * + * @NOTE Was DontMapToRange in SSCI. + */ + void blockRange(const uint8 from, const int16 count); + + /** + * Determines whether or not the given color has an + * active remapper. If it does not, it is treated as a + * skip color and the pixel is not drawn. + * + * @note SSCI uses a boolean array to decide whether a + * a pixel is remapped, but it is possible to get the + * same information from `_remaps`, as this function + * does. + * Presumably, the separate array was created for + * performance reasons, since this is called a lot in + * the most critical section of the renderer. + */ + inline bool remapEnabled(uint8 color) const { + const uint8 index = _remapEndColor - color; + assert(index < _remaps.size()); + return (_remaps[index]._type != kRemapNone); + } + + /** + * Calculates the correct color for a target by looking + * up the target color in the SingleRemap that controls + * the given sourceColor. If there is no remap for the + * given color, it will be treated as a skip color. + */ + inline uint8 remapColor(const uint8 sourceColor, const uint8 targetColor) const { + const uint8 index = _remapEndColor - sourceColor; + assert(index < _remaps.size()); + const SingleRemap &singleRemap = _remaps[index]; + assert(singleRemap._type != kRemapNone); + return singleRemap._remapColors[targetColor]; + } + + /** + * Updates all active remaps in response to a palette + * change or a remap settings change. + * + * `paletteChanged` is true if the next palette in + * GfxPalette32 has been previously modified by other + * palette operations. + */ + bool remapAllTables(const bool paletteUpdated); + +private: + typedef Common::Array<SingleRemap> SingleRemapsList; + + /** + * The first index of the remap area in the system + * palette. + */ + const uint8 _remapStartColor; + + /** + * The last index of the remap area in the system + * palette. + */ + uint8 _remapEndColor; + + /** + * The number of currently active remaps. + */ + uint8 _numActiveRemaps; + + /** + * The list of SingleRemaps. + */ + SingleRemapsList _remaps; + + /** + * If true, indicates that one or more SingleRemaps were + * reconfigured and all remaps need to be recalculated. + */ + bool _needsUpdate; + + /** + * The first color that is blocked from being used as a + * remap target color. + */ + uint8 _blockedRangeStart; + + /** + * The size of the range of blocked colors. If zero, + * all colors are potential targets for remapping. + */ + int16 _blockedRangeCount; +}; +} // End of namespace Sci +#endif diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp index fba0fa0422..c1644a5ea3 100644 --- a/engines/sci/graphics/screen_item32.cpp +++ b/engines/sci/graphics/screen_item32.cpp @@ -273,7 +273,9 @@ void ScreenItem::calcRects(const Plane &plane) { // Cel may use a coordinate system that is not the same size as the // script coordinate system (usually this means high-resolution // pictures with low-resolution scripts) - if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) { + if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) { + // high resolution coordinates + if (_useInsetRect) { const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth); const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); @@ -345,6 +347,8 @@ void ScreenItem::calcRects(const Plane &plane) { _ratioX = scaleX * celToScreenX; _ratioY = scaleY * celToScreenY; } else { + // low resolution coordinates + int displaceX = celObj._displace.x; if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) { displaceX = celObj._width - celObj._displace.x - 1; @@ -515,6 +519,25 @@ void ScreenItem::update(const reg_t object) { _deleted = 0; } +void ScreenItem::update() { + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane); + if (plane == nullptr) { + error("ScreenItem::update: Invalid plane %04x:%04x", PRINT_REG(_plane)); + } + + if (plane->_screenItemList.findByObject(_object) == nullptr) { + error("ScreenItem::update: %04x:%04x not in plane %04x:%04x", PRINT_REG(_object), PRINT_REG(_plane)); + } + + if (!_created) { + _updated = g_sci->_gfxFrameout->getScreenCount(); + } + _deleted = 0; + + delete _celObj; + _celObj = nullptr; +} + // TODO: This code is quite similar to calcRects, so try to deduplicate // if possible Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { @@ -563,7 +586,9 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { displaceX = celObj._width - displaceX - 1; } - if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) { + if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) { + // high resolution coordinates + if (_useInsetRect) { Ratio scriptToCelX(celObj._scaledWidth, scriptWidth); Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); @@ -597,6 +622,8 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { mulinc(nsRect, celToScriptX, celToScriptY); nsRect.translate(_position.x - displaceX, _position.y - displaceY); } else { + // low resolution coordinates + if (!scaleX.isOne() || !scaleY.isOne()) { mulinc(nsRect, scaleX, scaleY); // TODO: This was in the original code, baked into the @@ -630,23 +657,43 @@ ScreenItem *ScreenItemList::findByObject(const reg_t object) const { return *screenItemIt; } void ScreenItemList::sort() { - // TODO: SCI engine used _unsorted as an array of indexes into the - // list itself and then performed the same swap operations on the - // _unsorted array as the _storage array during sorting, but the - // only reason to do this would be if some of the pointers in the - // list were replaced so the pointer values themselves couldn’t - // simply be recorded and then restored later. It is not yet - // verified whether this simplification of the sort/unsort is - // safe. + if (size() < 2) { + return; + } + for (size_type i = 0; i < size(); ++i) { - _unsorted[i] = (*this)[i]; + _unsorted[i] = i; } - Common::sort(begin(), end(), sortHelper); + for (size_type i = size() - 1; i > 0; --i) { + bool swap = false; + + for (size_type j = 0; j < i; ++j) { + value_type &a = operator[](j); + value_type &b = operator[](j + 1); + + if (a == nullptr || *a > *b) { + SWAP(a, b); + SWAP(_unsorted[j], _unsorted[j + 1]); + swap = true; + } + } + + if (!swap) { + break; + } + } } void ScreenItemList::unsort() { + if (size() < 2) { + return; + } + for (size_type i = 0; i < size(); ++i) { - (*this)[i] = _unsorted[i]; + while (_unsorted[i] != i) { + SWAP(operator[](_unsorted[i]), operator[](i)); + SWAP(_unsorted[_unsorted[i]], _unsorted[i]); + } } } diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h index 91f54b48e9..2e44e418ce 100644 --- a/engines/sci/graphics/screen_item32.h +++ b/engines/sci/graphics/screen_item32.h @@ -236,6 +236,24 @@ public: return false; } + inline bool operator>(const ScreenItem &other) const { + if (_priority > other._priority) { + return true; + } + + if (_priority == other._priority) { + if (_position.y + _z > other._position.y + other._z) { + return true; + } + + if (_position.y + _z == other._position.y + other._z) { + return _object > other._object; + } + } + + return false; + } + /** * Calculates the dimensions and scaling parameters for * the screen item, using the given plane as the parent @@ -261,6 +279,12 @@ public: void update(const reg_t object); /** + * Updates the properties of the screen item for one not belonging + * to a VM object. Originally GraphicsMgr::UpdateScreenItem. + */ + void update(); + + /** * Gets the "now seen" rect for the screen item, which * represents the current size and position of the * screen item on the screen in script coordinates. @@ -273,12 +297,10 @@ public: typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase; class ScreenItemList : public ScreenItemListBase { - inline static bool sortHelper(const ScreenItem *a, const ScreenItem *b) { - return *a < *b; - } -public: - ScreenItem *_unsorted[250]; +private: + size_type _unsorted[250]; +public: ScreenItem *findByObject(const reg_t object) const; void sort(); void unsort(); diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index d1c223d5d5..277e6e93d0 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -39,18 +39,24 @@ namespace Sci { int16 GfxText32::_defaultFontId = 0; +int16 GfxText32::_scaledWidth = 0; +int16 GfxText32::_scaledHeight = 0; GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) : _segMan(segMan), _cache(fonts), - _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), - _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), // Not a typo, the original engine did not initialise height, only width _width(0), _text(""), _bitmap(NULL_REG) { _fontId = _defaultFontId; _font = _cache->getFont(_defaultFontId); + + if (_scaledWidth == 0) { + // initialize the statics + _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + } } reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling) { @@ -115,7 +121,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - int borderSize = 1; mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight)); CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo); @@ -132,7 +137,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false); _bitmap = bitmap.getObject(); - Buffer buffer(_width, _height, bitmap.getPixels()); // NOTE: The engine filled the bitmap pixels with 11 here, which is silly // because then it just erased the bitmap using the skip color. So we don't @@ -142,7 +146,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & erase(bitmapRect, false); _backColor = backColor; - view.draw(buffer, bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight)); + view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight)); if (_backColor != skipColor && _foreColor != skipColor) { erase(_textRect, false); @@ -153,7 +157,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & error("TODO: Implement transparent text"); } else { if (borderColor != -1) { - drawFrame(bitmapRect, borderSize, _borderColor, false); + drawFrame(bitmapRect, 1, _borderColor, false); } drawTextBox(); @@ -231,8 +235,11 @@ void GfxText32::drawTextBox() { int16 textRectWidth = _textRect.width(); _drawPosition.y = _textRect.top; uint charIndex = 0; - if (getLongest(&charIndex, textRectWidth) == 0) { - error("DrawTextBox GetLongest=0"); + + if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + if (getLongest(&charIndex, textRectWidth) == 0) { + error("DrawTextBox GetLongest=0"); + } } charIndex = 0; @@ -311,6 +318,10 @@ void GfxText32::drawText(const uint index, uint length) { ++text; --length; } + if (length > 0) { + ++text; + --length; + } } else { drawChar(currentChar); } @@ -498,7 +509,7 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const { --length; fontId = fontId * 10 + currentChar - '0'; - } while (length > 0 && currentChar >= '0' && currentChar <= '9'); + } while (length > 0 && *text >= '0' && *text <= '9'); if (length > 0) { font = _cache->getFont(fontId); @@ -506,7 +517,11 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const { } // Forward through any more unknown control character data - while (length > 0 && currentChar != '|') { + while (length > 0 && *text != '|') { + ++text; + --length; + } + if (length > 0) { ++text; --length; } @@ -514,8 +529,10 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const { width += font->getCharWidth(currentChar); } - currentChar = *text++; - --length; + if (length > 0) { + currentChar = *text++; + --length; + } } return width; @@ -573,11 +590,16 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, } } else { result.right = getTextWidth(0, 10000); - // NOTE: In the original engine code, the bottom was not decremented - // by 1, which means that the rect was actually a pixel taller than - // the height of the font. This was not the case in the other branch, - // which decremented the bottom by 1 at the end of the loop. - result.bottom = _font->getHeight() + 1; + + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + result.bottom = 0; + } else { + // NOTE: In the original engine code, the bottom was not decremented + // by 1, which means that the rect was actually a pixel taller than + // the height of the font. This was not the case in the other branch, + // which decremented the bottom by 1 at the end of the loop. + result.bottom = _font->getHeight() + 1; + } } if (doScaling) { @@ -593,14 +615,8 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { Common::Rect targetRect = doScaling ? scaleRect(rect) : rect; - byte *bitmap = _segMan->getHunkPointer(_bitmap); - byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); - - // NOTE: There is an extra optimisation within the SCI code to - // do a single memset if the scaledRect is the same size as - // the bitmap, not implemented here. - Buffer buffer(_width, _height, pixels); - buffer.fillRect(targetRect, _backColor); + BitmapResource bitmap(_bitmap); + bitmap.getBuffer().fillRect(targetRect, _backColor); } int16 GfxText32::getStringWidth(const Common::String &text) { @@ -635,5 +651,69 @@ int16 GfxText32::getTextCount(const Common::String &text, const uint index, cons return getTextCount(text, index, textRect, doScaling); } +void GfxText32::scrollLine(const Common::String &lineText, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir) { + BitmapResource bmr(_bitmap); + byte *pixels = bmr.getPixels(); + + int h = _font->getHeight(); + + if (dir == kScrollUp) { + // Scroll existing text down + for (int i = 0; i < (numLines - 1) * h; ++i) { + int y = _textRect.top + numLines * h - i - 1; + memcpy(pixels + y * _width + _textRect.left, + pixels + (y - h) * _width + _textRect.left, + _textRect.width()); + } + } else { + // Scroll existing text up + for (int i = 0; i < (numLines - 1) * h; ++i) { + int y = _textRect.top + i; + memcpy(pixels + y * _width + _textRect.left, + pixels + (y + h) * _width + _textRect.left, + _textRect.width()); + } + } + + Common::Rect lineRect = _textRect; + + if (dir == kScrollUp) { + lineRect.bottom = lineRect.top + h; + } else { + // It is unclear to me what the purpose of this bottom++ is. + // It does not seem to be the usual inc/exc issue. + lineRect.top += (numLines - 1) * h; + lineRect.bottom++; + } + + erase(lineRect, false); + + _drawPosition.x = _textRect.left; + _drawPosition.y = _textRect.top; + if (dir == kScrollDown) { + _drawPosition.y += (numLines - 1) * h; + } + + _foreColor = color; + _alignment = align; + //int fc = _foreColor; + + setFont(fontId); + + _text = lineText; + int16 textWidth = getTextWidth(0, lineText.size()); + + if (_alignment == kTextAlignCenter) { + _drawPosition.x += (_textRect.width() - textWidth) / 2; + } else if (_alignment == kTextAlignRight) { + _drawPosition.x += _textRect.width() - textWidth; + } + + //_foreColor = fc; + //setFont(fontId); + + drawText(0, lineText.size()); +} + } // End of namespace Sci diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 20adb3d7c7..a61760dd87 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -23,15 +23,23 @@ #ifndef SCI_GRAPHICS_TEXT32_H #define SCI_GRAPHICS_TEXT32_H +#include "sci/engine/state.h" #include "sci/graphics/celobj32.h" #include "sci/graphics/frameout.h" +#include "sci/graphics/helpers.h" namespace Sci { enum TextAlign { - kTextAlignLeft = 0, - kTextAlignCenter = 1, - kTextAlignRight = 2 + kTextAlignDefault = -1, + kTextAlignLeft = 0, + kTextAlignCenter = 1, + kTextAlignRight = 2 +}; + +enum ScrollDirection { + kScrollUp, + kScrollDown }; enum BitmapFlags { @@ -53,6 +61,7 @@ inline void set##property(uint##size value) {\ class BitmapResource { byte *_bitmap; reg_t _object; + Buffer _buffer; /** * Gets the size of the bitmap header for the current @@ -96,6 +105,8 @@ public: if (_bitmap == nullptr || getUncompressedDataOffset() != getBitmapHeaderSize()) { error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap)); } + + _buffer = Buffer(getWidth(), getHeight(), getPixels()); } /** @@ -103,7 +114,6 @@ public: * segment manager. */ inline BitmapResource(SegManager *segMan, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool remap) { - _object = segMan->allocateHunkEntry("Bitmap()", getBitmapSize(width, height)); _bitmap = segMan->getHunkPointer(_object); @@ -124,12 +134,18 @@ public: setControlOffset(0); setScaledWidth(scaledWidth); setScaledHeight(scaledHeight); + + _buffer = Buffer(getWidth(), getHeight(), getPixels()); } - reg_t getObject() const { + inline reg_t getObject() const { return _object; } + inline Buffer &getBuffer() { + return _buffer; + } + BITMAP_PROPERTY(16, Width, 0); BITMAP_PROPERTY(16, Height, 2); @@ -173,7 +189,7 @@ public: return READ_SCI11ENDIAN_UINT32(_bitmap + 20); } - void setHunkPaletteOffset(uint32 hunkPaletteOffset) { + inline void setHunkPaletteOffset(uint32 hunkPaletteOffset) { if (hunkPaletteOffset) { hunkPaletteOffset += getBitmapHeaderSize(); } @@ -351,15 +367,15 @@ public: /** * The size of the x-dimension of the coordinate system - * used by the text renderer. + * used by the text renderer. Static since it was global in SSCI. */ - int16 _scaledWidth; + static int16 _scaledWidth; /** * The size of the y-dimension of the coordinate system - * used by the text renderer. + * used by the text renderer. Static since it was global in SSCI. */ - int16 _scaledHeight; + static int16 _scaledHeight; /** * The currently active font resource used to write text @@ -456,6 +472,13 @@ public: * `textRect` using the given font. */ int16 getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling); + + /** + * Scroll up/down one line. `numLines` is the number of the lines in the + * textarea, and `textLine` contains the text to draw as the newly + * visible line. Originally FontMgr::DrawOneLine and FontMgr::UpOneLine. + */ + void scrollLine(const Common::String &textLine, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir); }; } // End of namespace Sci diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h index 91590208c1..96b48c0477 100644 --- a/engines/sci/graphics/view.h +++ b/engines/sci/graphics/view.h @@ -55,6 +55,7 @@ struct LoopInfo { class GfxScreen; class GfxPalette; +class Resource; /** * View class, handles loading of view resources and drawing contained cels to screen |