diff options
Diffstat (limited to 'engines/pegasus/graphics.cpp')
-rw-r--r-- | engines/pegasus/graphics.cpp | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/engines/pegasus/graphics.cpp b/engines/pegasus/graphics.cpp new file mode 100644 index 0000000000..175f15e91d --- /dev/null +++ b/engines/pegasus/graphics.cpp @@ -0,0 +1,337 @@ +/* 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. + * + * Additional copyright for this file: + * Copyright (C) 1995-1997 Presto Studios, Inc. + * + * 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 "common/events.h" +#include "common/file.h" +#include "common/textconsole.h" +#include "engines/util.h" + +#include "pegasus/elements.h" +#include "pegasus/graphics.h" + +namespace Pegasus { + +GraphicsManager::GraphicsManager(PegasusEngine *vm) : _vm(vm) { + initGraphics(640, 480, true, NULL); + + if (_vm->_system->getScreenFormat().bytesPerPixel == 1) + error("No true color mode available"); + + _backLayer = kMinAvailableOrder; + _frontLayer = kMaxAvailableOrder; + _firstDisplayElement = _lastDisplayElement = 0; + _workArea.create(640, 480, _vm->_system->getScreenFormat()); + _modifiedScreen = false; + _curSurface = &_workArea; + _erase = false; +} + +GraphicsManager::~GraphicsManager() { + _workArea.free(); +} + +void GraphicsManager::invalRect(const Common::Rect &rect) { + // We're using a simpler algorithm for dirty rect handling than the original + // The original was way too overcomplicated for what we need here now. + + if (_dirtyRect.width() == 0 || _dirtyRect.height() == 0) { + // We have no dirty rect, so this is now our dirty rect + _dirtyRect = rect; + } else { + // Expand our dirty rect to include rect + _dirtyRect.extend(rect); + } + + // Sanity check: clip our rect to the screen + _dirtyRect.right = MIN<int>(640, _dirtyRect.right); + _dirtyRect.bottom = MIN<int>(480, _dirtyRect.bottom); +} + +void GraphicsManager::addDisplayElement(DisplayElement *newElement) { + newElement->_elementOrder = CLIP<int>(newElement->_elementOrder, kMinAvailableOrder, kMaxAvailableOrder); + + if (_firstDisplayElement) { + DisplayElement *runner = _firstDisplayElement; + DisplayElement *lastRunner = 0; + + // Search for first element whose display order is greater than + // the new element's and add the new element just before it. + while (runner) { + if (newElement->_elementOrder < runner->_elementOrder) { + if (lastRunner) { + lastRunner->_nextElement = newElement; + newElement->_nextElement = runner; + } else { + newElement->_nextElement = _firstDisplayElement; + _firstDisplayElement = newElement; + } + break; + } + lastRunner = runner; + runner = runner->_nextElement; + } + + // If got here and runner == NULL, we ran through the whole list without + // inserting, so add at the end. + if (!runner) { + _lastDisplayElement->_nextElement = newElement; + _lastDisplayElement = newElement; + } + } else { + _firstDisplayElement = newElement; + _lastDisplayElement = newElement; + } + + newElement->_elementIsDisplaying = true; +} + +void GraphicsManager::removeDisplayElement(DisplayElement *oldElement) { + if (!_firstDisplayElement) + return; + + if (oldElement == _firstDisplayElement) { + if (oldElement == _lastDisplayElement) { + _firstDisplayElement = 0; + _lastDisplayElement = 0; + } else { + _firstDisplayElement = oldElement->_nextElement; + } + + invalRect(oldElement->_bounds); + } else { + // Scan list for element. + // If we get here, we know that the list has at least one item, and it + // is not the first item, so we can skip it. + DisplayElement *runner = _firstDisplayElement->_nextElement; + DisplayElement *lastRunner = _firstDisplayElement; + + while (runner) { + if (runner == oldElement) { + lastRunner->_nextElement = runner->_nextElement; + + if (oldElement == _lastDisplayElement) + _lastDisplayElement = lastRunner; + + invalRect(oldElement->_bounds); + break; + } + + lastRunner = runner; + runner = runner->_nextElement; + } + } + + oldElement->_nextElement = 0; + oldElement->_elementIsDisplaying = false; +} + +void GraphicsManager::updateDisplay() { + bool screenDirty = false; + + if (!_dirtyRect.isEmpty()) { + // Fill the dirty area with black if erase mode is enabled + if (_erase) + _workArea.fillRect(_dirtyRect, _workArea.format.RGBToColor(0, 0, 0)); + + for (DisplayElement *runner = _firstDisplayElement; runner != 0; runner = runner->_nextElement) { + Common::Rect bounds; + runner->getBounds(bounds); + + // TODO: Better logic; it does a bit more work than it probably needs to + // but it should work fine for now. + if (bounds.intersects(_dirtyRect) && runner->validToDraw(_backLayer, _frontLayer)) { + runner->draw(bounds); + screenDirty = true; + } + } + + // Copy only the dirty rect to the screen + if (screenDirty) + g_system->copyRectToScreen((byte *)_workArea.getBasePtr(_dirtyRect.left, _dirtyRect.top), _workArea.pitch, _dirtyRect.left, _dirtyRect.top, _dirtyRect.width(), _dirtyRect.height()); + + // Clear the dirty rect + _dirtyRect = Common::Rect(); + } + + if (screenDirty || _modifiedScreen) + g_system->updateScreen(); + + _modifiedScreen = false; +} + +void GraphicsManager::clearScreen() { + Graphics::Surface *screen = g_system->lockScreen(); + screen->fillRect(Common::Rect(0, 0, 640, 480), g_system->getScreenFormat().RGBToColor(0, 0, 0)); + g_system->unlockScreen(); + _modifiedScreen = true; +} + +DisplayElement *GraphicsManager::findDisplayElement(const DisplayElementID id) { + DisplayElement *runner = _firstDisplayElement; + + while (runner) { + if (runner->getObjectID() == id) + return runner; + runner = runner->_nextElement; + } + + return 0; +} + +void GraphicsManager::doFadeOutSync(const TimeValue, const TimeValue, uint32 color) { + if (color == 0) + color = g_system->getScreenFormat().RGBToColor(0, 0, 0); + + // HACK: Until fading out is done, white-out the screen here + Graphics::Surface *screen = g_system->lockScreen(); + screen->fillRect(Common::Rect(0, 0, 640, 480), color); + g_system->unlockScreen(); + g_system->updateScreen(); +} + +void GraphicsManager::doFadeInSync(const TimeValue, const TimeValue, uint32) { + // TODO +} + +void GraphicsManager::markCursorAsDirty() { + _modifiedScreen = true; +} + +void GraphicsManager::newShakePoint(int32 index1, int32 index2, int32 maxRadius) { + int32 index3 = (index1 + index2) >> 1; + + if (maxRadius == 0) { + _shakeOffsets[index3].x = ((_shakeOffsets[index1].x + _shakeOffsets[index2].x) >> 1); + _shakeOffsets[index3].y = ((_shakeOffsets[index1].y + _shakeOffsets[index2].y) >> 1); + } else { + double angle = (int32)(_vm->getRandomNumber(360 - 1) * 3.1415926535 / 180); + int32 radius = maxRadius; + _shakeOffsets[index3].x = (int32)(((_shakeOffsets[index1].x + _shakeOffsets[index2].x) >> 1) + + cos(angle) / 2 * radius); + _shakeOffsets[index3].y = (int32)(((_shakeOffsets[index1].y + _shakeOffsets[index2].y) >> 1) + + sin(angle) * radius); + } + + if (index1 < index3 - 1) + newShakePoint(index1, index3, maxRadius * 2 / 3); + + if (index3 < index2 - 1) + newShakePoint(index3, index2, maxRadius * 2 / 3); +} + +void GraphicsManager::shakeTheWorld(TimeValue duration, TimeScale scale) { + if (duration == 0 || scale == 0) + return; + + _shakeOffsets[0].x = 0; + _shakeOffsets[0].y = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) / 4].x = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) / 4].y = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) / 2].x = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) / 2].y = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) * 3 / 4].x = 0; + _shakeOffsets[(kMaxShakeOffsets - 1) * 3 / 4].y = 0; + _shakeOffsets[kMaxShakeOffsets - 1].x = 0; + _shakeOffsets[kMaxShakeOffsets - 1].y = 0; + + newShakePoint(0, (kMaxShakeOffsets - 1) / 4, 8); + newShakePoint((kMaxShakeOffsets - 1) / 4, (kMaxShakeOffsets - 1) / 2, 6); + newShakePoint((kMaxShakeOffsets - 1) / 2, (kMaxShakeOffsets - 1) * 3 / 4, 4); + newShakePoint((kMaxShakeOffsets - 1) * 3 / 4, kMaxShakeOffsets - 1, 3); + + Common::Point lastOffset(0, 0); + + // Store the current screen for later use + Graphics::Surface oldScreen; + Graphics::Surface *curScreen = g_system->lockScreen(); + oldScreen.copyFrom(*curScreen); + g_system->unlockScreen(); + + // Convert to millis + duration = duration * 1000 / scale; + + uint32 startTime = g_system->getMillis(); + + while (g_system->getMillis() < startTime + duration) { + Common::Point thisOffset = _shakeOffsets[(g_system->getMillis() - startTime) * (kMaxShakeOffsets - 1) / duration]; + if (thisOffset != lastOffset) { + // Fill the screen with black + Graphics::Surface *screen = g_system->lockScreen(); + screen->fillRect(Common::Rect(0, 0, 640, 480), g_system->getScreenFormat().RGBToColor(0, 0, 0)); + g_system->unlockScreen(); + + // Calculate the src/dst offsets and the width/height + int32 srcOffsetX, dstOffsetX, width; + + if (thisOffset.x > 0) { + srcOffsetX = 0; + dstOffsetX = thisOffset.x; + width = 640 - dstOffsetX; + } else { + srcOffsetX = -thisOffset.x; + dstOffsetX = 0; + width = 640 - srcOffsetX; + } + + int32 srcOffsetY, dstOffsetY, height; + + if (thisOffset.y > 0) { + srcOffsetY = 0; + dstOffsetY = thisOffset.y; + height = 480 - dstOffsetY; + } else { + srcOffsetY = -thisOffset.y; + dstOffsetY = 0; + height = 480 - srcOffsetY; + } + + // Now copy to the screen + g_system->copyRectToScreen((byte *)oldScreen.getBasePtr(srcOffsetX, srcOffsetY), oldScreen.pitch, + dstOffsetX, dstOffsetY, width, height); + g_system->updateScreen(); + + lastOffset = thisOffset; + } + + g_system->delayMillis(10); + } + + if (lastOffset.x != 0 || lastOffset.y != 0) { + g_system->copyRectToScreen((byte *)oldScreen.pixels, oldScreen.pitch, 0, 0, 640, 480); + g_system->updateScreen(); + } + + oldScreen.free(); +} + +void GraphicsManager::enableErase() { + _erase = true; +} + +void GraphicsManager::disableErase() { + _erase = false; +} + +} // End of namespace Pegasus |