diff options
Diffstat (limited to 'engines/zvision/graphics')
-rw-r--r-- | engines/zvision/graphics/render_manager.cpp | 526 | ||||
-rw-r--r-- | engines/zvision/graphics/render_manager.h | 328 | ||||
-rw-r--r-- | engines/zvision/graphics/render_table.cpp | 240 | ||||
-rw-r--r-- | engines/zvision/graphics/render_table.h | 85 |
4 files changed, 1179 insertions, 0 deletions
diff --git a/engines/zvision/graphics/render_manager.cpp b/engines/zvision/graphics/render_manager.cpp new file mode 100644 index 0000000000..af8ca7fd64 --- /dev/null +++ b/engines/zvision/graphics/render_manager.cpp @@ -0,0 +1,526 @@ +/* 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 "common/scummsys.h" + +#include "zvision/render_manager.h" + +#include "zvision/lzss_read_stream.h" + +#include "common/file.h" +#include "common/system.h" +#include "common/stream.h" + +#include "engines/util.h" + +#include "graphics/decoders/tga.h" + + +namespace ZVision { + +RenderManager::RenderManager(OSystem *system, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat) + : _system(system), + _workingWidth(workingWindow.width()), + _workingHeight(workingWindow.height()), + _screenCenterX(_workingWidth / 2), + _screenCenterY(_workingHeight / 2), + _workingWindow(workingWindow), + _pixelFormat(pixelFormat), + _backgroundWidth(0), + _backgroundHeight(0), + _backgroundInverseVelocity(0), + _backgroundOffset(0, 0), + _accumulatedVelocityMilliseconds(0), + _renderTable(_workingWidth, _workingHeight) { + + _workingWindowBuffer.create(_workingWidth, _workingHeight, _pixelFormat); + _backBuffer.create(windowWidth, windowHeight, pixelFormat); +} + +RenderManager::~RenderManager() { + _workingWindowBuffer.free(); + _currentBackground.free(); + _backBuffer.free(); + + for (AlphaEntryMap::iterator iter = _alphaDataEntries.begin(); iter != _alphaDataEntries.end(); ++iter) { + iter->_value.data->free(); + delete iter->_value.data; + } +} + +void RenderManager::update(uint deltaTimeInMillis) { + // An inverse velocity of 0 would be infinitely fast, so we'll let 0 mean no velocity. + if (_backgroundInverseVelocity != 0) { + _accumulatedVelocityMilliseconds += deltaTimeInMillis; + + uint absVelocity = uint(abs(_backgroundInverseVelocity)); + + int numberOfSteps = 0; + while (_accumulatedVelocityMilliseconds >= absVelocity) { + _accumulatedVelocityMilliseconds -= absVelocity; + numberOfSteps++; + } + + // Choose the direction of movement using the sign of the velocity + moveBackground(_backgroundInverseVelocity < 0 ? -numberOfSteps : numberOfSteps); + } +} + +void RenderManager::renderBackbufferToScreen() { + if (!_workingWindowDirtyRect.isEmpty()) { + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + _renderTable.mutateImage((uint16 *)_workingWindowBuffer.getPixels(), (uint16 *)_backBuffer.getBasePtr(_workingWindow.left + _workingWindowDirtyRect.left, _workingWindow.top + _workingWindowDirtyRect.top), _backBuffer.w, _workingWindowDirtyRect); + } else { + _backBuffer.copyRectToSurface(_workingWindowBuffer.getBasePtr(_workingWindowDirtyRect.left, _workingWindowDirtyRect.top), _workingWindowBuffer.pitch, _workingWindow.left + _workingWindowDirtyRect.left, _workingWindow.top + _workingWindowDirtyRect.top, _workingWindowDirtyRect.width(), _workingWindowDirtyRect.height()); + } + + // Translate the working window dirty rect to screen coords + _workingWindowDirtyRect.translate(_workingWindow.left, _workingWindow.top); + // Then extend the backbuffer dirty rect to contain it + if (_backBufferDirtyRect.isEmpty()) { + _backBufferDirtyRect = _workingWindowDirtyRect; + } else { + _backBufferDirtyRect.extend(_workingWindowDirtyRect); + } + + // Clear the dirty rect + _workingWindowDirtyRect = Common::Rect(); + } + + // TODO: Add menu rendering + + // Render alpha entries + processAlphaEntries(); + + if (!_backBufferDirtyRect.isEmpty()) { + _system->copyRectToScreen(_backBuffer.getBasePtr(_backBufferDirtyRect.left, _backBufferDirtyRect.top), _backBuffer.pitch, _backBufferDirtyRect.left, _backBufferDirtyRect.top, _backBufferDirtyRect.width(), _backBufferDirtyRect.height()); + _backBufferDirtyRect = Common::Rect(); + } +} + +void RenderManager::processAlphaEntries() { + // TODO: Add dirty rectangling support. AKA only draw an entry if the _backbufferDirtyRect intersects/contains the entry Rect + + for (AlphaEntryMap::iterator iter = _alphaDataEntries.begin(); iter != _alphaDataEntries.end(); ++iter) { + uint32 destOffset = 0; + uint32 sourceOffset = 0; + uint16 *backbufferPtr = (uint16 *)_backBuffer.getBasePtr(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top); + uint16 *entryPtr = (uint16 *)iter->_value.data->getPixels(); + + for (int32 y = 0; y < iter->_value.height; ++y) { + for (int32 x = 0; x < iter->_value.width; ++x) { + uint16 color = entryPtr[sourceOffset + x]; + if (color != iter->_value.alphaColor) { + backbufferPtr[destOffset + x] = color; + } + } + + destOffset += _backBuffer.w; + sourceOffset += iter->_value.width; + } + + if (_backBufferDirtyRect.isEmpty()) { + _backBufferDirtyRect = Common::Rect(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top, iter->_value.destX + _workingWindow.left + iter->_value.width, iter->_value.destY + _workingWindow.top + iter->_value.height); + } else { + _backBufferDirtyRect.extend(Common::Rect(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top, iter->_value.destX + _workingWindow.left + iter->_value.width, iter->_value.destY + _workingWindow.top + iter->_value.height)); + } + } +} + +void RenderManager::clearWorkingWindowTo555Color(uint16 color) { + uint32 workingWindowSize = _workingWidth * _workingHeight; + byte r, g, b; + Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0).colorToRGB(color, r, g, b); + uint16 colorIn565 = _pixelFormat.RGBToColor(r, g, b); + uint16 *bufferPtr = (uint16 *)_workingWindowBuffer.getPixels(); + + for (uint32 i = 0; i < workingWindowSize; ++i) { + bufferPtr[i] = colorIn565; + } +} + +void RenderManager::renderSubRectToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap) { + int16 subRectX = 0; + int16 subRectY = 0; + + // Take care of negative destinations + if (destinationX < 0) { + subRectX = -destinationX; + destinationX = 0; + } else if (destinationX >= surface.w) { + // Take care of extreme positive destinations + destinationX -= surface.w; + } + + // Take care of negative destinations + if (destinationY < 0) { + subRectY = -destinationY; + destinationY = 0; + } else if (destinationY >= surface.h) { + // Take care of extreme positive destinations + destinationY -= surface.h; + } + + if (wrap) { + _backgroundWidth = surface.w; + _backgroundHeight = surface.h; + + if (destinationX > 0) { + // Move destinationX to 0 + subRectX = surface.w - destinationX; + destinationX = 0; + } + + if (destinationY > 0) { + // Move destinationY to 0 + subRectY = surface.h - destinationY; + destinationY = 0; + } + } + + // Clip subRect to working window bounds + Common::Rect subRect(subRectX, subRectY, subRectX + _workingWidth, subRectY + _workingHeight); + + if (!wrap) { + // Clip to image bounds + subRect.clip(surface.w, surface.h); + } + + // Check destRect for validity + if (!subRect.isValidRect() || subRect.isEmpty()) + return; + + copyRectToWorkingWindow((const uint16 *)surface.getBasePtr(subRect.left, subRect.top), destinationX, destinationY, surface.w, subRect.width(), subRect.height()); +} + +void RenderManager::renderImageToScreen(const Common::String &fileName, int16 destinationX, int16 destinationY, bool wrap) { + Graphics::Surface surface; + readImageToSurface(fileName, surface); + + renderSubRectToScreen(surface, destinationX, destinationY, wrap); +} + +void RenderManager::renderImageToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap) { + renderSubRectToScreen(surface, destinationX, destinationY, wrap); +} + +void RenderManager::readImageToSurface(const Common::String &fileName, Graphics::Surface &destination) { + Common::File file; + + if (!file.open(fileName)) { + warning("Could not open file %s", fileName.c_str()); + return; + } + + // Read the magic number + // Some files are true TGA, while others are TGZ + uint32 fileType = file.readUint32BE(); + + uint32 imageWidth; + uint32 imageHeight; + Graphics::TGADecoder tga; + uint16 *buffer; + bool isTransposed = _renderTable.getRenderState() == RenderTable::PANORAMA; + // All ZVision images are in RGB 555 + Graphics::PixelFormat pixelFormat555 = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); + destination.format = pixelFormat555; + + bool isTGZ; + + // Check for TGZ files + if (fileType == MKTAG('T', 'G', 'Z', '\0')) { + isTGZ = true; + + // TGZ files have a header and then Bitmap data that is compressed with LZSS + uint32 decompressedSize = file.readSint32LE(); + imageWidth = file.readSint32LE(); + imageHeight = file.readSint32LE(); + + LzssReadStream lzssStream(&file); + buffer = (uint16 *)(new uint16[decompressedSize]); + lzssStream.read(buffer, decompressedSize); + } else { + isTGZ = false; + + // Reset the cursor + file.seek(0); + + // Decode + if (!tga.loadStream(file)) { + warning("Error while reading TGA image"); + return; + } + + Graphics::Surface tgaSurface = *(tga.getSurface()); + imageWidth = tgaSurface.w; + imageHeight = tgaSurface.h; + + buffer = (uint16 *)tgaSurface.getPixels(); + } + + // Flip the width and height if transposed + if (isTransposed) { + uint16 temp = imageHeight; + imageHeight = imageWidth; + imageWidth = temp; + } + + // If the destination internal buffer is the same size as what we're copying into it, + // there is no need to free() and re-create + if (imageWidth != destination.w || imageHeight != destination.h) { + destination.create(imageWidth, imageHeight, pixelFormat555); + } + + // If transposed, 'un-transpose' the data while copying it to the destination + // Otherwise, just do a simple copy + if (isTransposed) { + uint16 *dest = (uint16 *)destination.getPixels(); + + for (uint32 y = 0; y < imageHeight; ++y) { + uint32 columnIndex = y * imageWidth; + + for (uint32 x = 0; x < imageWidth; ++x) { + dest[columnIndex + x] = buffer[x * imageHeight + y]; + } + } + } else { + memcpy(destination.getPixels(), buffer, imageWidth * imageHeight * _pixelFormat.bytesPerPixel); + } + + // Cleanup + if (isTGZ) { + delete[] buffer; + } else { + tga.destroy(); + } + + // Convert in place to RGB 565 from RGB 555 + destination.convertToInPlace(_pixelFormat); +} + +void RenderManager::copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height) { + uint32 destOffset = 0; + uint32 sourceOffset = 0; + uint16 *workingWindowBufferPtr = (uint16 *)_workingWindowBuffer.getBasePtr(destX, destY); + + for (int32 y = 0; y < height; ++y) { + for (int32 x = 0; x < width; ++x) { + workingWindowBufferPtr[destOffset + x] = buffer[sourceOffset + x]; + } + + destOffset += _workingWidth; + sourceOffset += imageWidth; + } + + if (_workingWindowDirtyRect.isEmpty()) { + _workingWindowDirtyRect = Common::Rect(destX, destY, destX + width, destY + height); + } else { + _workingWindowDirtyRect.extend(Common::Rect(destX, destY, destX + width, destY + height)); + } + + // TODO: Remove this from release. It's here to make sure code that uses this function clips their destinations correctly + assert(_workingWindowDirtyRect.width() <= _workingWidth && _workingWindowDirtyRect.height() <= _workingHeight); +} + +void RenderManager::copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height, int16 alphaColor, uint32 idNumber) { + AlphaDataEntry entry; + entry.alphaColor = alphaColor; + entry.data = new Graphics::Surface(); + entry.data->create(width, height, _pixelFormat); + entry.destX = destX; + entry.destY = destY; + entry.width = width; + entry.height = height; + + uint32 sourceOffset = 0; + uint32 destOffset = 0; + uint16 *surfacePtr = (uint16 *)entry.data->getPixels(); + + for (int32 y = 0; y < height; ++y) { + for (int32 x = 0; x < width; ++x) { + surfacePtr[destOffset + x] = buffer[sourceOffset + x]; + } + + destOffset += width; + sourceOffset += imageWidth; + } + + _alphaDataEntries[idNumber] = entry; +} + +Common::Rect RenderManager::renderTextToWorkingWindow(uint32 idNumber, const Common::String &text, TruetypeFont *font, int destX, int destY, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap) { + AlphaDataEntry entry; + entry.alphaColor = 0; + entry.destX = destX; + entry.destY = destY; + + // Draw the text to the working window + entry.data = font->drawTextToSurface(text, textColor, maxWidth, maxHeight, align, wrap); + entry.width = entry.data->w; + entry.height = entry.data->h; + + _alphaDataEntries[idNumber] = entry; + + return Common::Rect(destX, destY, destX + entry.width, destY + entry.height); +} + +const Common::Point RenderManager::screenSpaceToImageSpace(const Common::Point &point) { + // Convert from screen space to working window space + Common::Point newPoint(point - Common::Point(_workingWindow.left, _workingWindow.top)); + + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint); + } + + if (state == RenderTable::PANORAMA) { + newPoint -= (Common::Point(_screenCenterX, 0) - _backgroundOffset); + } else if (state == RenderTable::TILT) { + newPoint -= (Common::Point(0, _screenCenterY) - _backgroundOffset); + } + + if (newPoint.x < 0) + newPoint.x += _backgroundWidth; + else if (newPoint.x >= _backgroundWidth) + newPoint.x -= _backgroundWidth; + if (newPoint.y < 0) + newPoint.y += _backgroundHeight; + else if (newPoint.y >= _backgroundHeight) + newPoint.y -= _backgroundHeight; + + return newPoint; +} + +const Common::Point RenderManager::imageSpaceToWorkingWindowSpace(const Common::Point &point) { + Common::Point newPoint(point); + + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA) { + newPoint += (Common::Point(_screenCenterX, 0) - _backgroundOffset); + } else if (state == RenderTable::TILT) { + newPoint += (Common::Point(0, _screenCenterY) - _backgroundOffset); + } + + return newPoint; +} + +bool RenderManager::clipRectToWorkingWindow(Common::Rect &rect) { + if (!_workingWindow.contains(rect)) { + return false; + } + + // We can't clip against the actual working window rect because it's in screen space + // But rect is in working window space + rect.clip(_workingWidth, _workingHeight); + return true; +} + +RenderTable *RenderManager::getRenderTable() { + return &_renderTable; +} + +void RenderManager::setBackgroundImage(const Common::String &fileName) { + readImageToSurface(fileName, _currentBackground); + + moveBackground(0); +} + +void RenderManager::setBackgroundPosition(int offset) { + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::TILT) { + _backgroundOffset.x = 0; + _backgroundOffset.y = offset; + } else if (state == RenderTable::PANORAMA) { + _backgroundOffset.x = offset; + _backgroundOffset.y = 0; + } else { + _backgroundOffset.x = 0; + _backgroundOffset.y = 0; + } +} + +void RenderManager::setBackgroundVelocity(int velocity) { + // setBackgroundVelocity(0) will be called quite often, so make sure + // _backgroundInverseVelocity isn't already 0 to prevent an extraneous assignment + if (velocity == 0) { + if (_backgroundInverseVelocity != 0) { + _backgroundInverseVelocity = 0; + } + } else { + _backgroundInverseVelocity = 1000 / velocity; + } +} + +void RenderManager::moveBackground(int offset) { + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::TILT) { + _backgroundOffset += Common::Point(0, offset); + + _backgroundOffset.y = CLIP<int16>(_backgroundOffset.y, _screenCenterY, (int16)_backgroundHeight - _screenCenterY); + + renderImageToScreen(_currentBackground, 0, _screenCenterY - _backgroundOffset.y, true); + } else if (state == RenderTable::PANORAMA) { + _backgroundOffset += Common::Point(offset, 0); + + if (_backgroundOffset.x <= -_backgroundWidth) + _backgroundOffset.x += _backgroundWidth; + else if (_backgroundOffset.x >= _backgroundWidth) + _backgroundOffset.x -= _backgroundWidth; + + renderImageToScreen(_currentBackground, _screenCenterX - _backgroundOffset.x, 0, true); + } else { + renderImageToScreen(_currentBackground, 0, 0); + } +} + +uint32 RenderManager::getCurrentBackgroundOffset() { + RenderTable::RenderState state = _renderTable.getRenderState(); + + if (state == RenderTable::PANORAMA) { + return _backgroundOffset.x; + } else if (state == RenderTable::TILT) { + return _backgroundOffset.y; + } else { + return 0; + } +} + +Graphics::Surface *RenderManager::tranposeSurface(const Graphics::Surface *surface) { + Graphics::Surface *tranposedSurface = new Graphics::Surface(); + tranposedSurface->create(surface->h, surface->w, surface->format); + + const uint16 *source = (const uint16 *)surface->getPixels(); + uint16 *dest = (uint16 *)tranposedSurface->getPixels(); + + for (uint32 y = 0; y < tranposedSurface->h; ++y) { + uint32 columnIndex = y * tranposedSurface->w; + + for (uint32 x = 0; x < tranposedSurface->w; ++x) { + dest[columnIndex + x] = source[x * surface->w + y]; + } + } + + return tranposedSurface; +} + +} // End of namespace ZVision diff --git a/engines/zvision/graphics/render_manager.h b/engines/zvision/graphics/render_manager.h new file mode 100644 index 0000000000..111bf6276c --- /dev/null +++ b/engines/zvision/graphics/render_manager.h @@ -0,0 +1,328 @@ +/* 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 ZVISION_RENDER_MANAGER_H +#define ZVISION_RENDER_MANAGER_H + +#include "zvision/render_table.h" +#include "zvision/truetype_font.h" + +#include "common/rect.h" +#include "common/hashmap.h" + +#include "graphics/surface.h" + + +class OSystem; + +namespace Common { +class String; +class SeekableReadStream; +} + +namespace Video { +class VideoDecoder; +} + +namespace ZVision { + +class RenderManager { +public: + RenderManager(OSystem *system, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat); + ~RenderManager(); + +private: + struct AlphaDataEntry { + Graphics::Surface *data; + uint16 alphaColor; + uint16 destX; + uint16 destY; + uint16 width; + uint16 height; + }; + + typedef Common::HashMap<uint32, AlphaDataEntry> AlphaEntryMap; + +private: + OSystem *_system; + const Graphics::PixelFormat _pixelFormat; + + // A buffer the exact same size as the workingWindow + // This buffer stores everything un-warped, then does a warp at the end of the frame + Graphics::Surface _workingWindowBuffer; + // A buffer representing the entire screen. Any graphical updates are first done with this buffer + // before actually being blitted to the screen + Graphics::Surface _backBuffer; + // A list of Alpha Entries that need to be blitted to the backbuffer + AlphaEntryMap _alphaDataEntries; + + // A rectangle representing the portion of the working window where the pixels have been changed since last frame + Common::Rect _workingWindowDirtyRect; + // A rectangle representing the portion of the backbuffer where the pixels have been changed since last frame + Common::Rect _backBufferDirtyRect; + + /** Width of the working window. Saved to prevent extraneous calls to _workingWindow.width() */ + const int _workingWidth; + /** Height of the working window. Saved to prevent extraneous calls to _workingWindow.height() */ + const int _workingHeight; + /** Center of the screen in the x direction */ + const int _screenCenterX; + /** Center of the screen in the y direction */ + const int _screenCenterY; + + /** + * A Rectangle centered inside the actual window. All in-game coordinates + * are given in this coordinate space. Also, all images are clipped to the + * edges of this Rectangle + */ + const Common::Rect _workingWindow; + /** Used to warp the background image */ + RenderTable _renderTable; + + Graphics::Surface _currentBackground; + /** The (x1,y1) coordinates of the subRectangle of the background that is currently displayed on the screen */ + Common::Point _backgroundOffset; + /** The width of the current background image */ + uint16 _backgroundWidth; + /** The height of the current background image */ + uint16 _backgroundHeight; + + /** + * The "velocity" at which the background image is panning. We actually store the inverse of velocity (ms/pixel instead of pixels/ms) + * because it allows you to accumulate whole pixels 'steps' instead of rounding pixels every frame + */ + int _backgroundInverseVelocity; + /** Holds any 'leftover' milliseconds between frames */ + uint _accumulatedVelocityMilliseconds; + +public: + void initialize(); + /** + * Rotates the background image in accordance to the current _backgroundInverseVelocity + * + * @param deltaTimeInMillis The amount of time that has passed since the last frame + */ + void update(uint deltaTimeInMillis); + + /** + * Renders the current state of the backbuffer to the screen + */ + void renderBackbufferToScreen(); + + /** + * Renders all AlphaEntries to the backbuffer + */ + void processAlphaEntries(); + /** + * Clears the AlphaEntry list + */ + void clearAlphaEntries() { _alphaDataEntries.clear(); } + /** + * Removes a specific AlphaEntry from the list + * + * @param idNumber The id number identifing the AlphaEntry + */ + void removeAlphaEntry(uint32 idNumber) { _alphaDataEntries.erase(idNumber); } + + /** + * Copies a sub-rectangle of a buffer to the working window + * + * @param buffer The pixel data to copy to the working window + * @param destX The X destination in the working window where the subRect of data should be put + * @param destY The Y destination in the working window where the subRect of data should be put + * @param imageWidth The width of the source image + * @param width The width of the sub rectangle + * @param height The height of the sub rectangle + */ + void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height); + /** + * Copies a sub-rectangle of a buffer to the working window with binary alpha support. + * + * @param buffer The pixel data to copy to the working window + * @param destX The X destination in the working window where the subRect of data should be put + * @param destY The Y destination in the working window where the subRect of data should be put + * @param imageWidth The width of the source image + * @param width The width of the sub rectangle + * @param height The height of the sub rectangle + * @param alphaColor The color to interpret as meaning 'transparent' + * @param idNumber A unique identifier for the data being copied over. + */ + void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height, int16 alphaColor, uint32 idNumber); + + /** + * Renders the supplied text to the working window + * + * @param idNumber A unique identifier for the text + * @param text The text to be rendered + * @param font The font to use to render the text + * @param destX The X destination in the working window where the text should be rendered + * @param destY The Y destination in the working window where the text should be rendered + * @param textColor The color to render the text with (in RBG 565) + * @param maxWidth The max width the text should take up. + * @param maxHeight The max height the text should take up. + * @param align The alignment of the text within the bounds of maxWidth + * @param wrap If true, any words extending past maxWidth will wrap to a new line. If false, ellipses will be rendered to show that the text didn't fit + * @return A rectangle representing where the text was drawn in the working window + */ + Common::Rect renderTextToWorkingWindow(uint32 idNumber, const Common::String &text, TruetypeFont *font, int destX, int destY, uint16 textColor, int maxWidth, int maxHeight = -1, Graphics::TextAlign align = Graphics::kTextAlignLeft, bool wrap = true); + + /** + * Fills the entire workingWindow with the specified color. Internally, the color + * will be converted to RGB 565 and then blitted. + * + * @param color The color to fill the working window with. (In RGB 555) + */ + void clearWorkingWindowTo555Color(uint16 color); + + /** + * Blits the image or a portion of the image to the backbuffer. Actual screen updates won't happen until the end of the frame. + * The image will be clipped to fit inside the working window. Coords are in working window space, not screen space! + * + * @param fileName Name of the image file + * @param destinationX X position where the image should be put. Coords are in working window space, not screen space! + * @param destinationY Y position where the image should be put. Coords are in working window space, not screen space! + */ + void renderImageToScreen(const Common::String &fileName, int16 destinationX, int16 destinationY, bool wrap = false); + + /** + * Blits the image or a portion of the image to the backbuffer. Actual screen updates won't happen until the end of the frame. + * The image will be clipped to fit inside the working window. Coords are in working window space, not screen space! + * + * @param stream Surface to read the image data from + * @param destinationX X position where the image should be put. Coords are in working window space, not screen space! + * @param destinationY Y position where the image should be put. Coords are in working window space, not screen space! + */ + void renderImageToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap = false); + + /** + * Sets the current background image to be used by the RenderManager and immediately + * blits it to the screen. (It won't show up until the end of the frame) + * + * @param fileName The name of the image file + */ + void setBackgroundImage(const Common::String &fileName); + + /** + * Set the background position (_backgroundOffset). If the current RenderState is PANORAMA, the offset + * will be in the horizontal direction. If the current RenderState is TILT, the offset will be in the + * vertical direction. + * + * This method will not render anything on the screen. So if nothing else is called that renders the + * background, the change won't be seen until next frame. + * + * @param offset The amount to offset the background + */ + void setBackgroundPosition(int offset); + + /** + * Set the background scroll velocity. Negative velocities correspond to left / up scrolling and + * positive velocities correspond to right / down scrolling + * + * @param velocity Velocity + */ + void setBackgroundVelocity(int velocity); + + /** + * Converts a point in screen coordinate space to image coordinate space + * + * @param point Point in screen coordinate space + * @return Point in image coordinate space + */ + const Common::Point screenSpaceToImageSpace(const Common::Point &point); + /** + * Converts a point in image coordinate space to ***PRE-WARP*** + * working window coordinate space + * + * @param point Point in image coordinate space + * @return Point in PRE-WARP working window coordinate space + */ + const Common::Point imageSpaceToWorkingWindowSpace(const Common::Point &point); + + /** + * Clip a rectangle to the working window. If it returns false, the original rect + * is not inside the working window. + * + * @param rect The rectangle to clip against the working window + * @return Is rect at least partially inside the working window (true) or completely outside (false) + */ + bool clipRectToWorkingWindow(Common::Rect &rect); + + RenderTable *getRenderTable(); + uint32 getCurrentBackgroundOffset(); + const Graphics::Surface *getBackBuffer() { return &_backBuffer; } + + /** + * Creates a copy of surface and transposes the data. + * + * Note: The user is responsible for calling free() on the returned surface + * and then deleting it + * + * @param surface The data to be transposed + * @return A copy of the surface with the data transposed + */ + static Graphics::Surface *tranposeSurface(const Graphics::Surface *surface); + +private: + /** + * Renders a subRectangle of an image to the backbuffer. The destinationRect and SubRect + * will be clipped to image bound and to working window bounds + * + * @param buffer Pointer to (0, 0) of the image data + * @param imageWidth The width of the original image (not of the subRectangle) + * @param imageHeight The width of the original image (not of the subRectangle) + * @param horizontalPitch The horizontal pitch of the original image + * @param destinationX The x coordinate (in working window space) of where to put the final image + * @param destinationY The y coordinate (in working window space) of where to put the final image + * @param subRectangle A rectangle representing the part of the image that should be rendered + * @param wrap Should the image wrap (tile) if it doesn't completely fill the screen? + */ + void renderSubRectToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap); + + /** + * Reads an image file pixel data into a Surface buffer. In the process + * it converts the pixel data from RGB 555 to RGB 565. Also, if the image + * is transposed, it will un-transpose the pixel data. The function will + * call destination::create() if the dimensions of destination do not match + * up with the dimensions of the image. + * + * @param fileName The name of a .tga file + * @param destination A reference to the Surface to store the pixel data in + */ + void readImageToSurface(const Common::String &fileName, Graphics::Surface &destination); + + /** + * Move the background image by an offset. If we are currently in Panorama mode, + * the offset will correspond to a horizontal motion. If we are currently in Tilt mode, + * the offset will correspond to a vertical motion. This function should not be called + * if we are in Flat mode. + * + * The RenderManager will take care of wrapping the image. + * Ex: If the image has width 1400px, it is legal to offset 1500px. + * + * @param offset The amount to move the background + */ + void moveBackground(int offset); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/graphics/render_table.cpp b/engines/zvision/graphics/render_table.cpp new file mode 100644 index 0000000000..b6a6a3d2bb --- /dev/null +++ b/engines/zvision/graphics/render_table.cpp @@ -0,0 +1,240 @@ +/* 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 "common/scummsys.h" + +#include "zvision/render_table.h" + +#include "common/rect.h" + +#include "graphics/colormasks.h" + + +namespace ZVision { + +RenderTable::RenderTable(uint numColumns, uint numRows) + : _numRows(numRows), + _numColumns(numColumns), + _renderState(FLAT) { + assert(numRows != 0 && numColumns != 0); + + _internalBuffer = new Common::Point[numRows * numColumns]; +} + +RenderTable::~RenderTable() { + delete[] _internalBuffer; +} + +void RenderTable::setRenderState(RenderState newState) { + _renderState = newState; + + switch (newState) { + case PANORAMA: + _panoramaOptions.fieldOfView = 27.0f; + _panoramaOptions.linearScale = 0.55f; + _panoramaOptions.reverse = false; + break; + case TILT: + _tiltOptions.fieldOfView = 27.0f; + _tiltOptions.linearScale = 0.55f; + _tiltOptions.reverse = false; + break; + case FLAT: + // Intentionally left empty + break; + } +} + +const Common::Point RenderTable::convertWarpedCoordToFlatCoord(const Common::Point &point) { + // If we're outside the range of the RenderTable, no warping is happening. Return the maximum image coords + if (point.x >= (int16)_numColumns || point.y >= (int16)_numRows || point.x < 0 || point.y < 0) { + int16 x = CLIP<int16>(point.x, 0, (int16)_numColumns); + int16 y = CLIP<int16>(point.y, 0, (int16)_numRows); + return Common::Point(x, y); + } + + uint32 index = point.y * _numColumns + point.x; + + Common::Point newPoint(point); + newPoint.x += _internalBuffer[index].x; + newPoint.y += _internalBuffer[index].y; + + return newPoint; +} + +uint16 mixTwoRGB(uint16 colorOne, uint16 colorTwo, float percentColorOne) { + assert(percentColorOne < 1.0f); + + float rOne = float((colorOne & Graphics::ColorMasks<555>::kRedMask) >> Graphics::ColorMasks<555>::kRedShift); + float rTwo = float((colorTwo & Graphics::ColorMasks<555>::kRedMask) >> Graphics::ColorMasks<555>::kRedShift); + float gOne = float((colorOne & Graphics::ColorMasks<555>::kGreenMask) >> Graphics::ColorMasks<555>::kGreenShift); + float gTwo = float((colorTwo & Graphics::ColorMasks<555>::kGreenMask) >> Graphics::ColorMasks<555>::kGreenShift); + float bOne = float((colorOne & Graphics::ColorMasks<555>::kBlueMask) >> Graphics::ColorMasks<555>::kBlueShift); + float bTwo = float((colorTwo & Graphics::ColorMasks<555>::kBlueMask) >> Graphics::ColorMasks<555>::kBlueShift); + + float rFinal = rOne * percentColorOne + rTwo * (1.0f - percentColorOne); + float gFinal = gOne * percentColorOne + gTwo * (1.0f - percentColorOne); + float bFinal = bOne * percentColorOne + bTwo * (1.0f - percentColorOne); + + uint16 returnColor = (byte(rFinal + 0.5f) << Graphics::ColorMasks<555>::kRedShift) | + (byte(gFinal + 0.5f) << Graphics::ColorMasks<555>::kGreenShift) | + (byte(bFinal + 0.5f) << Graphics::ColorMasks<555>::kBlueShift); + + return returnColor; +} + +void RenderTable::mutateImage(uint16 *sourceBuffer, uint16* destBuffer, uint32 destWidth, const Common::Rect &subRect) { + uint32 destOffset = 0; + + for (int16 y = subRect.top; y < subRect.bottom; ++y) { + uint32 sourceOffset = y * _numColumns; + + for (int16 x = subRect.left; x < subRect.right; ++x) { + uint32 normalizedX = x - subRect.left; + uint32 index = sourceOffset + x; + + // RenderTable only stores offsets from the original coordinates + uint32 sourceYIndex = y + _internalBuffer[index].y; + uint32 sourceXIndex = x + _internalBuffer[index].x; + + destBuffer[destOffset + normalizedX] = sourceBuffer[sourceYIndex * _numColumns + sourceXIndex]; + } + + destOffset += destWidth; + } +} + +void RenderTable::generateRenderTable() { + switch (_renderState) { + case ZVision::RenderTable::PANORAMA: + generatePanoramaLookupTable(); + break; + case ZVision::RenderTable::TILT: + generateTiltLookupTable(); + break; + case ZVision::RenderTable::FLAT: + // Intentionally left empty + break; + } +} + +void RenderTable::generatePanoramaLookupTable() { + memset(_internalBuffer, 0, _numRows * _numColumns * sizeof(uint16)); + + float halfWidth = (float)_numColumns / 2.0f; + float halfHeight = (float)_numRows / 2.0f; + + float fovInRadians = (_panoramaOptions.fieldOfView * M_PI / 180.0f); + float cylinderRadius = halfHeight / tan(fovInRadians); + + for (uint x = 0; x < _numColumns; ++x) { + // Add an offset of 0.01 to overcome zero tan/atan issue (vertical line on half of screen) + // Alpha represents the horizontal angle between the viewer at the center of a cylinder and x + float alpha = atan(((float)x - halfWidth + 0.01f) / cylinderRadius); + + // To get x in cylinder coordinates, we just need to calculate the arc length + // We also scale it by _panoramaOptions.linearScale + int32 xInCylinderCoords = int32(floor((cylinderRadius * _panoramaOptions.linearScale * alpha) + halfWidth)); + + float cosAlpha = cos(alpha); + + for (uint y = 0; y < _numRows; ++y) { + // To calculate y in cylinder coordinates, we can do similar triangles comparison, + // comparing the triangle from the center to the screen and from the center to the edge of the cylinder + int32 yInCylinderCoords = int32(floor(halfHeight + ((float)y - halfHeight) * cosAlpha)); + + uint32 index = y * _numColumns + x; + + // Only store the (x,y) offsets instead of the absolute positions + _internalBuffer[index].x = xInCylinderCoords - x; + _internalBuffer[index].y = yInCylinderCoords - y; + } + } +} + +void RenderTable::generateTiltLookupTable() { + float halfWidth = (float)_numColumns / 2.0f; + float halfHeight = (float)_numRows / 2.0f; + + float fovInRadians = (_tiltOptions.fieldOfView * M_PI / 180.0f); + float cylinderRadius = halfWidth / tan(fovInRadians); + + for (uint y = 0; y < _numRows; ++y) { + + // Add an offset of 0.01 to overcome zero tan/atan issue (horizontal line on half of screen) + // Alpha represents the vertical angle between the viewer at the center of a cylinder and y + float alpha = atan(((float)y - halfHeight + 0.01f) / cylinderRadius); + + // To get y in cylinder coordinates, we just need to calculate the arc length + // We also scale it by _tiltOptions.linearScale + int32 yInCylinderCoords = int32(floor((cylinderRadius * _tiltOptions.linearScale * alpha) + halfHeight)); + + float cosAlpha = cos(alpha); + uint32 columnIndex = y * _numColumns; + + for (uint x = 0; x < _numColumns; ++x) { + // To calculate x in cylinder coordinates, we can do similar triangles comparison, + // comparing the triangle from the center to the screen and from the center to the edge of the cylinder + int32 xInCylinderCoords = int32(floor(halfWidth + ((float)x - halfWidth) * cosAlpha)); + + uint32 index = columnIndex + x; + + // Only store the (x,y) offsets instead of the absolute positions + _internalBuffer[index].x = xInCylinderCoords - x; + _internalBuffer[index].y = yInCylinderCoords - y; + } + } +} + +void RenderTable::setPanoramaFoV(float fov) { + assert(fov > 0.0f); + + _panoramaOptions.fieldOfView = fov; +} + +void RenderTable::setPanoramaScale(float scale) { + assert(scale > 0.0f); + + _panoramaOptions.linearScale = scale; +} + +void RenderTable::setPanoramaReverse(bool reverse) { + _panoramaOptions.reverse = reverse; +} + +void RenderTable::setTiltFoV(float fov) { + assert(fov > 0.0f); + + _tiltOptions.fieldOfView = fov; +} + +void RenderTable::setTiltScale(float scale) { + assert(scale > 0.0f); + + _tiltOptions.linearScale = scale; +} + +void RenderTable::setTiltReverse(bool reverse) { + _tiltOptions.reverse = reverse; +} + +} // End of namespace ZVision diff --git a/engines/zvision/graphics/render_table.h b/engines/zvision/graphics/render_table.h new file mode 100644 index 0000000000..898091193a --- /dev/null +++ b/engines/zvision/graphics/render_table.h @@ -0,0 +1,85 @@ +/* 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 ZVISION_RENDER_TABLE_H +#define ZVISION_RENDER_TABLE_H + +#include "common/rect.h" + + +namespace ZVision { + +class RenderTable { +public: + RenderTable(uint numRows, uint numColumns); + ~RenderTable(); + +public: + enum RenderState { + PANORAMA, + TILT, + FLAT + }; + +private: + uint _numColumns, _numRows; + Common::Point *_internalBuffer; + RenderState _renderState; + + struct { + float fieldOfView; + float linearScale; + bool reverse; + } _panoramaOptions; + + // TODO: See if tilt and panorama need to have separate options + struct { + float fieldOfView; + float linearScale; + bool reverse; + } _tiltOptions; + +public: + RenderState getRenderState() { return _renderState; } + void setRenderState(RenderState newState); + + const Common::Point convertWarpedCoordToFlatCoord(const Common::Point &point); + + void mutateImage(uint16 *sourceBuffer, uint16* destBuffer, uint32 destWidth, const Common::Rect &subRect); + void generateRenderTable(); + + void setPanoramaFoV(float fov); + void setPanoramaScale(float scale); + void setPanoramaReverse(bool reverse); + + void setTiltFoV(float fov); + void setTiltScale(float scale); + void setTiltReverse(bool reverse); + +private: + void generatePanoramaLookupTable(); + void generateTiltLookupTable(); +}; + +} // End of namespace ZVision + +#endif |