/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/config-manager.h" #include "common/system.h" #include "gui/message.h" #include "engines/util.h" #if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS) || defined(USE_GLES2) #include "graphics/opengl/context.h" #endif #include "freescape/gfx.h" #include "freescape/objects/object.h" namespace Freescape { const Graphics::PixelFormat getRGBAPixelFormat() { #ifdef SCUMM_BIG_ENDIAN return Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); #else return Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24); #endif } Renderer::Renderer(int screenW, int screenH, Common::RenderMode renderMode, bool authenticGraphics) { _screenW = screenW; _screenH = screenH; _currentPixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); _palettePixelFormat = Graphics::PixelFormat(3, 8, 8, 8, 0, 0, 8, 16, 0); _keyColor = -1; _inkColor = -1; _paperColor = -1; _underFireBackgroundColor = -1; _palette = nullptr; _colorMap = nullptr; _colorRemaps = nullptr; _renderMode = renderMode; _isAccelerated = false; _authenticGraphics = authenticGraphics; for (int i = 0; i < 16; i++) { for (int j = 0; j < 128; j++) { _stipples[i][j] = 0; } _colorPair[i] = 0; } _scale = 1; } Renderer::~Renderer() {} extern byte getCPCPixel(byte cpc_byte, int index, bool mode0); byte getCPCStipple(byte cpc_byte, int back, int fore) { int c0 = getCPCPixel(cpc_byte, 0, true); assert(c0 == back || c0 == fore); int c1 = getCPCPixel(cpc_byte, 1, true); assert(c1 == back || c1 == fore); int c2 = getCPCPixel(cpc_byte, 2, true); assert(c2 == back || c2 == fore); int c3 = getCPCPixel(cpc_byte, 3, true); assert(c3 == back || c3 == fore); byte st = 0; if (c0 == fore) st = st | 0x3; if (c1 == fore) st = st | (0x3 << 2); if (c2 == fore) st = st | (0x3 << 4); if (c3 == fore) st = st | (0x3 << 6); return st; } byte getCGAPixel(byte x, int index) { if (index == 0) return (x >> 0) & 0x3; else if (index == 1) return (x >> 2) & 0x3; else if (index == 2) return (x >> 4) & 0x3; else if (index == 3) return (x >> 6) & 0x3; else error("Invalid index %d requested", index); } byte getC64Pixel(byte x, int index) { // Unknown return 0; } byte getCGAStipple(byte x, int back, int fore) { int c0 = getCGAPixel(x, 0); assert(c0 == back || c0 == fore || back == fore); int c1 = getCGAPixel(x, 1); assert(c1 == back || c1 == fore || back == fore); int c2 = getCGAPixel(x, 2); assert(c2 == back || c2 == fore || back == fore); int c3 = getCGAPixel(x, 3); assert(c3 == back || c3 == fore || back == fore); byte st = 0; if (c0 == fore) st = st | 0x3; if (c1 == fore) st = st | (0x3 << 2); if (c2 == fore) st = st | (0x3 << 4); if (c3 == fore) st = st | (0x3 << 6); return st; } byte getC64Stipple(byte x, int back, int fore) { int c0 = x & 1 ? fore : back; //getC64Pixel(x, 0); assert(c0 == back || c0 == fore || back == fore); int c1 = (x >> 1) & 1 ? fore : back; //getC64Pixel(x, 1); assert(c1 == back || c1 == fore || back == fore); int c2 = (x >> 2) & 1 ? fore : back; //getC64Pixel(x, 2); assert(c2 == back || c2 == fore || back == fore); int c3 = (x >> 3) & 1 ? fore : back; //getC64Pixel(x, 3); assert(c3 == back || c3 == fore || back == fore); byte st = 0; if (c0 == fore) st = st | 0x3; if (c1 == fore) st = st | (0x3 << 2); if (c2 == fore) st = st | (0x3 << 4); if (c3 == fore) st = st | (0x3 << 6); return st; } void Renderer::clearColorPairArray() { for (int i = 0; i < 16; i++) _colorPair[i] = 0; } int getC64Color(uint8 index, bool isBackground) { if (index < 4) return index; if (isBackground) { switch (index) { case 4: return 1; case 5: // OK return 2; case 6: // OK return 0; case 7: // OK return 1; case 8: return 1; // ?? case 9: // OK return 3; case 10: // ?? return 0; case 11: return 0; case 12: return 0; case 13: // OK return 3; case 14: return 0; default: error("invalid c64 color index %d", index); } } switch (index) { case 4: return 0; case 5: // OK return 0; case 6: // OK return 3; case 7: // OK return 2; case 8: return 3; // ?? case 9: // OK return 2; case 10: // ?? return 0; case 11: return 0; case 12: return 0; case 13: return 2; case 14: return 0; default: error("invalid c64 color index %d", index); } error("unreachable"); } void Renderer::fillColorPairArray() { for (int i = 0; i < 15; i++) { byte *entry = (*_colorMap)[i]; int c1 = -1; int c2 = -1; if (_renderMode == Common::kRenderCGA) c1 = getCGAPixel(entry[0], 0); else if (_renderMode == Common::kRenderCPC) c1 = getCPCPixel(entry[0], 0, true); else if (_renderMode == Common::kRenderC64) { c1 = getC64Color(i, false); c2 = getC64Color(i, true); } else error("Not implemented"); if (_renderMode != Common::kRenderC64) { for (int j = 0; j < 4 && c2 == -1; j++) { int k, c; for (k = 0; k < 4; k++) { if (_renderMode == Common::kRenderCGA) c = getCGAPixel(entry[j], k); else if (_renderMode == Common::kRenderCPC) c = getCPCPixel(entry[j], k, true); else error("Not implemented"); if (c1 != c) { c2 = c; break; } } if (k != 4) break; } // The Castle Master CPC release needs the following workaround if (c2 < 0) c2 = c1; } assert((c1 < 16) & (c2 < 16)); _colorPair[i] = byte(c1) | (byte(c2) << 4); } } uint16 duplicate_bits(uint8 byte) { uint16 result = 0; for (int i = 0; i < 8; i++) { // Extract the bit at position i uint8 bit = (byte >> i) & 1; // Duplicate the bit uint16 duplicated_bits = (bit << 1) | bit; // Position the duplicated bits in the appropriate place in the result result |= (duplicated_bits << (2 * i)); } return result; } void Renderer::scaleStipplePattern(byte originalPattern[128], byte newPattern[128]) { // Initialize the new pattern to all 0 memset(newPattern, 0, 128); for (int i = 0; i < 64; i++) { // Duplicate the bits of the original pattern uint16 duplicated_bits = duplicate_bits(originalPattern[i]); // Position the duplicated bits in the appropriate place in the new pattern newPattern[2 * i] = (duplicated_bits >> 8) & 0xff; newPattern[2 * i + 1] = duplicated_bits & 0xff; } } void Renderer::setColorMap(ColorMap *colorMap_) { _colorMap = colorMap_; if (_renderMode == Common::kRenderZX || _renderMode == Common::kRenderHercG) { for (int i = 0; i < 15; i++) { byte *entry = (*_colorMap)[i]; for (int j = 0; j < 128; j++) _stipples[i][j] = entry[(j / 4) % 4]; } } else if (_renderMode == Common::kRenderCPC) { fillColorPairArray(); for (int i = 4; i < 15; i++) { byte pair = _colorPair[i]; byte c1 = pair & 0xf; byte c2 = (pair >> 4) & 0xf; byte *entry = (*_colorMap)[i]; for (int j = 0; j < 128; j++) _stipples[i][j] = getCPCStipple(entry[(j / 8) % 4], c1, c2); } } else if (_renderMode == Common::kRenderCGA) { fillColorPairArray(); for (int i = 4; i < 15; i++) { byte pair = _colorPair[i]; byte c1 = pair & 0xf; byte c2 = (pair >> 4) & 0xf; byte *entry = (*_colorMap)[i]; for (int j = 0; j < 128; j++) _stipples[i][j] = getCGAStipple(entry[(j / 8) % 4], c1, c2); } } else if (_renderMode == Common::kRenderC64) { fillColorPairArray(); for (int i = 4; i < 15; i++) { byte pair = _colorPair[i]; byte c1 = pair & 0xf; byte c2 = (pair >> 4) & 0xf; byte *entry = (*_colorMap)[i]; for (int j = 0; j < 128; j++) _stipples[i][j] = getC64Stipple(entry[(j / 8) % 4], c1, c2); } } if (_isAccelerated && _authenticGraphics) { for (int i = 1; i <= 14; i++) { scaleStipplePattern(_stipples[i], _stipples[15]); memcpy(_stipples[i], _stipples[15], 128); scaleStipplePattern(_stipples[i], _stipples[15]); memcpy(_stipples[i], _stipples[15], 128); } scaleStipplePattern(_defaultStippleArray, _stipples[15]); memcpy(_defaultStippleArray, _stipples[15], 128); } } void Renderer::readFromPalette(uint8 index, uint8 &r, uint8 &g, uint8 &b) { r = _palette[3 * index + 0]; g = _palette[3 * index + 1]; b = _palette[3 * index + 2]; } uint8 Renderer::indexFromColor(uint8 r, uint8 g, uint8 b) { for (int i = 0; i < 16; i++) { if (r == _palette[3 * i + 0] && g == _palette[3 * i + 1] && b == _palette[3 * i + 2]) return i; } warning("color %x %x %x not found", r, g, b); return 0; } void Renderer::setColorRemaps(ColorReMap *colorRemaps) { _colorRemaps = colorRemaps; if (_renderMode == Common::kRenderZX) { for (auto &it : *_colorRemaps) { if (it._key == 1) _paperColor = it._value; else if (it._key == 3) _inkColor = it._value; } } } bool Renderer::getRGBAtCGA(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &r2, uint8 &g2, uint8 &b2, byte *&stipple) { if (index == _keyColor) return false; assert(_renderMode == Common::kRenderCGA); stipple = (byte *)_stipples[index - 1]; byte pair = _colorPair[index - 1]; byte c1 = pair & 0xf; byte c2 = (pair >> 4) & 0xf; readFromPalette(c1, r1, g1, b1); readFromPalette(c2, r2, g2, b2); if (r1 == r2 && g1 == g2 && b1 == b2) { stipple = nullptr; } return true; } bool Renderer::getRGBAtC64(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &r2, uint8 &g2, uint8 &b2, byte *&stipple) { if (index == _keyColor) return false; if (_colorRemaps && _colorRemaps->contains(index)) { index = (*_colorRemaps)[index]; if (index == 0) { r1 = g1 = b1 = 0; r2 = r1; g2 = g1; b2 = b1; stipple = nullptr; return true; } readFromPalette(index, r1, g1, b1); r2 = r1; g2 = g1; b2 = b1; stipple = nullptr; return true; } assert(_renderMode == Common::kRenderC64); uint8 i1, i2; stipple = (byte *)_stipples[index - 1]; byte pair = _colorPair[index - 1]; i1 = pair & 0xf; i2 = (pair >> 4) & 0xf; selectColorFromFourColorPalette(i1, r1, g1, b1); selectColorFromFourColorPalette(i2, r2, g2, b2); if (r1 == r2 && g1 == g2 && b1 == b2) stipple = nullptr; return true; } bool Renderer::getRGBAtZX(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &r2, uint8 &g2, uint8 &b2, byte *&stipple) { if (index == _keyColor) return false; byte *entry = (*_colorMap)[index - 1]; if (entry[0] == 0 && entry[1] == 0 && entry[2] == 0 && entry[3] == 0) { readFromPalette(_paperColor, r1, g1, b1); readFromPalette(_paperColor, r2, g2, b2); stipple = nullptr; return true; } if (entry[0] == 0xff && entry[1] == 0xff && entry[2] == 0xff && entry[3] == 0xff) { readFromPalette(_inkColor, r1, g1, b1); readFromPalette(_inkColor, r2, g2, b2); stipple = nullptr; return true; } stipple = (byte *)_stipples[index - 1]; readFromPalette(_paperColor, r1, g1, b1); readFromPalette(_inkColor, r2, g2, b2); if (r1 == r2 && g1 == g2 && b1 == g2) { stipple = nullptr; } return true; } bool Renderer::getRGBAtHercules(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &r2, uint8 &g2, uint8 &b2, byte *&stipple) { if (index == _keyColor) return false; byte *entry = (*_colorMap)[index - 1]; if (entry[0] == 0 && entry[1] == 0 && entry[2] == 0 && entry[3] == 0) { readFromPalette(0, r1, g1, b1); readFromPalette(0, r2, g2, b2); return true; } if (entry[0] == 0xff && entry[1] == 0xff && entry[2] == 0xff && entry[3] == 0xff) { readFromPalette(1, r1, g1, b1); readFromPalette(1, r2, g2, b2); return true; } stipple = (byte *)_stipples[index - 1]; readFromPalette(0, r1, g1, b1); readFromPalette(1, r2, g2, b2); return true; } void Renderer::selectColorFromFourColorPalette(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1) { if (index == 0) { r1 = 0; g1 = 0; b1 = 0; } else if (index == 1) { readFromPalette(_underFireBackgroundColor, r1, g1, b1); } else if (index == 2) { readFromPalette(_paperColor, r1, g1, b1); } else if (index == 3) { readFromPalette(_inkColor, r1, g1, b1); } else error("Invalid color"); } bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &r2, uint8 &g2, uint8 &b2, byte *&stipple) { if (index == _keyColor) return false; if (_colorRemaps && _colorRemaps->contains(index)) { index = (*_colorRemaps)[index]; if (index == 0) { r1 = g1 = b1 = 0; r2 = r1; g2 = g1; b2 = b1; stipple = nullptr; return true; } readFromPalette(index, r1, g1, b1); r2 = r1; g2 = g1; b2 = b1; stipple = nullptr; return true; } assert(_renderMode == Common::kRenderCPC); stipple = (byte *)_stipples[index - 1]; byte *entry = (*_colorMap)[index - 1]; uint8 i1 = getCPCPixel(entry[0], 0, true); uint8 i2 = getCPCPixel(entry[0], 1, true); selectColorFromFourColorPalette(i1, r1, g1, b1); selectColorFromFourColorPalette(i2, r2, g2, b2); if (r1 == r2 && g1 == g2 && b1 == b2) { stipple = nullptr; } return true; } uint8 Renderer::mapEGAColor(uint8 index) { byte *entry = (*_colorMap)[index - 1]; uint8 color = 0; uint8 acc = 1; for (int i = 0; i < 4; i++) { byte be = *entry; assert(be == 0 || be == 0xff); if (be == 0xff) color = color + acc; acc = acc << 1; entry++; } assert(color < 16); return color; } bool Renderer::getRGBAtEGA(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &r2, uint8 &g2, uint8 &b2) { uint8 color; if (_colorPair[index] > 0) { color = mapEGAColor(_colorPair[index] & 0xf); readFromPalette(color, r1, g1, b1); color = mapEGAColor(_colorPair[index] >> 4); readFromPalette(color, r2, g2, b2); } else { color = mapEGAColor(index); if (_colorRemaps && _colorRemaps->contains(color)) { color = (*_colorRemaps)[color]; } readFromPalette(color, r1, g1, b1); r2 = r1; g2 = g1; b2 = b1; } return true; } bool Renderer::getRGBAt(uint8 index, uint8 ecolor, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &r2, uint8 &g2, uint8 &b2, byte *&stipple) { if (index == _keyColor && ecolor == 0) return false; if (index == 0 && ecolor == 0) { readFromPalette(0, r1, g1, b1); r2 = r1; g2 = g1; b2 = b1; return true; } if (_renderMode == Common::kRenderAmiga || _renderMode == Common::kRenderAtariST) { if (_colorPair[index] > 0) { int color = 0; color = _colorPair[index] & 0xf; readFromPalette(color, r1, g1, b1); color = _colorPair[index] >> 4; readFromPalette(color, r2, g2, b2); return true; } else if (_colorRemaps && _colorRemaps->contains(index)) { int color = (*_colorRemaps)[index]; _texturePixelFormat.colorToRGB(color, r1, g1, b1); } else readFromPalette(index, r1, g1, b1); if (ecolor > 0) readFromPalette(ecolor, r2, g2, b2); else { r2 = r1; g2 = g1; b2 = b1; } return true; } else if (_renderMode == Common::kRenderEGA) return getRGBAtEGA(index, r1, g1, b1, r2, g2, b2); else if (_renderMode == Common::kRenderC64) return getRGBAtC64(index, r1, g1, b1, r2, g2, b2, stipple); else if (_renderMode == Common::kRenderCGA) return getRGBAtCGA(index, r1, g1, b1, r2, g2, b2, stipple); else if (_renderMode == Common::kRenderCPC) return getRGBAtCPC(index, r1, g1, b1, r2, g2, b2, stipple); else if (_renderMode == Common::kRenderZX) return getRGBAtZX(index, r1, g1, b1, r2, g2, b2, stipple); else if (_renderMode == Common::kRenderHercG) return getRGBAtHercules(index, r1, g1, b1, r2, g2, b2, stipple); error("Invalid or unsupported render mode"); } void Renderer::flipVertical(Graphics::Surface *s) { for (int y = 0; y < s->h / 2; ++y) { // Flip the lines byte *line1P = (byte *)s->getBasePtr(0, y); byte *line2P = (byte *)s->getBasePtr(0, s->h - y - 1); for (int x = 0; x < s->pitch; ++x) SWAP(line1P[x], line2P[x]); } } Graphics::Surface *Renderer::convertImageFormatIfNecessary(Graphics::ManagedSurface *msurface) { if (!msurface) return nullptr; Graphics::Surface *surface = new Graphics::Surface(); surface->copyFrom(msurface->rawSurface()); byte *palette = (byte *)malloc(sizeof(byte) * 16 * 3); msurface->grabPalette(palette, 0, 16); // Maximum should be 16 colours surface->convertToInPlace(_texturePixelFormat, palette, 16); free(palette); return surface; } Common::Rect Renderer::viewport() const { return _screenViewport; } bool Renderer::computeScreenViewport() { int32 screenWidth = g_system->getWidth(); int32 screenHeight = g_system->getHeight(); Common::Rect viewport; if (g_system->getFeatureState(OSystem::kFeatureAspectRatioCorrection)) { // Aspect ratio correction int32 viewportWidth = MIN(screenWidth, screenHeight * float(4) / 3); int32 viewportHeight = MIN(screenHeight, screenWidth * float(3) / 3); viewport = Common::Rect(viewportWidth, viewportHeight); // Pillarboxing viewport.translate((screenWidth - viewportWidth) / 2, (screenHeight - viewportHeight) / 2); } else { // Aspect ratio correction disabled, just stretch viewport = Common::Rect(screenWidth, screenHeight); } if (viewport == _screenViewport) { return false; } _screenViewport = viewport; return true; } void Renderer::renderPyramid(const Math::Vector3d &origin, const Math::Vector3d &size, const Common::Array *ordinates, Common::Array *colours, Common::Array *ecolours, int type) { Math::Vector3d vertices[8] = { origin, origin, origin, origin, origin, origin, origin, origin }; switch (type) { default: error("Invalid pyramid type: %d", type); case kEastPyramidType: vertices[0] += Math::Vector3d(0, 0, size.z()); vertices[1] += Math::Vector3d(0, size.y(), size.z()); vertices[2] += Math::Vector3d(0, size.y(), 0); vertices[4] += Math::Vector3d(size.x(), (*ordinates)[0], (*ordinates)[3]); vertices[5] += Math::Vector3d(size.x(), (*ordinates)[2], (*ordinates)[3]); vertices[6] += Math::Vector3d(size.x(), (*ordinates)[2], (*ordinates)[1]); vertices[7] += Math::Vector3d(size.x(), (*ordinates)[0], (*ordinates)[1]); break; case kWestPyramidType: vertices[0] += Math::Vector3d(size.x(), 0, 0); vertices[1] += Math::Vector3d(size.x(), size.y(), 0); vertices[2] += Math::Vector3d(size.x(), size.y(), size.z()); vertices[3] += Math::Vector3d(size.x(), 0, size.z()); vertices[4] += Math::Vector3d(0, (*ordinates)[0], (*ordinates)[1]); vertices[5] += Math::Vector3d(0, (*ordinates)[2], (*ordinates)[1]); vertices[6] += Math::Vector3d(0, (*ordinates)[2], (*ordinates)[3]); vertices[7] += Math::Vector3d(0, (*ordinates)[0], (*ordinates)[3]); break; case kUpPyramidType: vertices[1] += Math::Vector3d(size.x(), 0, 0); vertices[2] += Math::Vector3d(size.x(), 0, size.z()); vertices[3] += Math::Vector3d(0, 0, size.z()); vertices[4] += Math::Vector3d((*ordinates)[0], size.y(), (*ordinates)[1]); vertices[5] += Math::Vector3d((*ordinates)[2], size.y(), (*ordinates)[1]); vertices[6] += Math::Vector3d((*ordinates)[2], size.y(), (*ordinates)[3]); vertices[7] += Math::Vector3d((*ordinates)[0], size.y(), (*ordinates)[3]); break; case kDownPyramidType: vertices[0] += Math::Vector3d(size.x(), size.y(), 0); vertices[1] += Math::Vector3d(0, size.y(), 0); vertices[2] += Math::Vector3d(0, size.y(), size.z()); vertices[3] += Math::Vector3d(size.x(), size.y(), size.z()); vertices[4] += Math::Vector3d((*ordinates)[2], 0, (*ordinates)[1]); vertices[5] += Math::Vector3d((*ordinates)[0], 0, (*ordinates)[1]); vertices[6] += Math::Vector3d((*ordinates)[0], 0, (*ordinates)[3]); vertices[7] += Math::Vector3d((*ordinates)[2], 0, (*ordinates)[3]); break; case kNorthPyramidType: vertices[0] += Math::Vector3d(0, size.y(), 0); vertices[1] += Math::Vector3d(size.x(), size.y(), 0); vertices[2] += Math::Vector3d(size.x(), 0, 0); vertices[4] += Math::Vector3d((*ordinates)[0], (*ordinates)[3], size.z()); vertices[5] += Math::Vector3d((*ordinates)[2], (*ordinates)[3], size.z()); vertices[6] += Math::Vector3d((*ordinates)[2], (*ordinates)[1], size.z()); vertices[7] += Math::Vector3d((*ordinates)[0], (*ordinates)[1], size.z()); break; case kSouthPyramidType: vertices[0] += Math::Vector3d(0, 0, size.z()); vertices[1] += Math::Vector3d(size.x(), 0, size.z()); vertices[2] += Math::Vector3d(size.x(), size.y(), size.z()); vertices[3] += Math::Vector3d(0, size.y(), size.z()); vertices[4] += Math::Vector3d((*ordinates)[0], (*ordinates)[1], 0); vertices[5] += Math::Vector3d((*ordinates)[2], (*ordinates)[1], 0); vertices[6] += Math::Vector3d((*ordinates)[2], (*ordinates)[3], 0); vertices[7] += Math::Vector3d((*ordinates)[0], (*ordinates)[3], 0); break; } Common::Array face; byte *stipple = nullptr; uint8 r1, g1, b1, r2, g2, b2; uint color = (*colours)[0]; uint ecolor = ecolours ? (*ecolours)[0] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.push_back(vertices[4]); face.push_back(vertices[5]); face.push_back(vertices[1]); face.push_back(vertices[0]); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } face.clear(); } color = (*colours)[1]; ecolor = ecolours ? (*ecolours)[1] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.push_back(vertices[5]); face.push_back(vertices[6]); face.push_back(vertices[2]); face.push_back(vertices[1]); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } face.clear(); } color = (*colours)[2]; ecolor = ecolours ? (*ecolours)[2] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.push_back(vertices[6]); face.push_back(vertices[7]); face.push_back(vertices[3]); face.push_back(vertices[2]); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } face.clear(); } color = (*colours)[3]; ecolor = ecolours ? (*ecolours)[3] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.push_back(vertices[7]); face.push_back(vertices[4]); face.push_back(vertices[0]); face.push_back(vertices[3]); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } face.clear(); } color = (*colours)[4]; ecolor = ecolours ? (*ecolours)[4] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.push_back(vertices[0]); face.push_back(vertices[1]); face.push_back(vertices[2]); face.push_back(vertices[3]); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } face.clear(); } color = (*colours)[5]; ecolor = ecolours ? (*ecolours)[5] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.push_back(vertices[7]); face.push_back(vertices[6]); face.push_back(vertices[5]); face.push_back(vertices[4]); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } } } void Renderer::renderCube(const Math::Vector3d &originalOrigin, const Math::Vector3d &size, Common::Array *colours, Common::Array *ecolours, float offset) { Math::Vector3d origin = originalOrigin; byte *stipple = nullptr; uint8 r1, g1, b1, r2, g2, b2; Common::Array face; uint color = (*colours)[0]; uint ecolor = ecolours ? (*ecolours)[0] : 0; if (size.x() <= 1) { origin.x() += offset; } else if (size.y() <= 1) { origin.y() += offset; } else if (size.z() <= 1) { origin.z() += offset; } if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.push_back(origin); face.push_back(Math::Vector3d(origin.x(), origin.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x(), origin.y() + size.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x(), origin.y() + size.y(), origin.z())); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } } color = (*colours)[1]; ecolor = ecolours ? (*ecolours)[1] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.clear(); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y() + size.y(), origin.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y() + size.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y(), origin.z())); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } } color = (*colours)[2]; ecolor = ecolours ? (*ecolours)[2] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.clear(); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y(), origin.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x(), origin.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x(), origin.y(), origin.z())); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } } color = (*colours)[3]; ecolor = ecolours ? (*ecolours)[3] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.clear(); face.push_back(Math::Vector3d(origin.x(), origin.y() + size.y(), origin.z())); face.push_back(Math::Vector3d(origin.x(), origin.y() + size.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y() + size.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y() + size.y(), origin.z())); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } } color = (*colours)[4]; ecolor = ecolours ? (*ecolours)[4] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.clear(); face.push_back(Math::Vector3d(origin.x(), origin.y() + size.y(), origin.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y() + size.y(), origin.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y(), origin.z())); face.push_back(origin); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } } color = (*colours)[5]; ecolor = ecolours ? (*ecolours)[5] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); face.clear(); face.push_back(Math::Vector3d(origin.x(), origin.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x() + size.x(), origin.y() + size.y(), origin.z() + size.z())); face.push_back(Math::Vector3d(origin.x(), origin.y() + size.y(), origin.z() + size.z())); renderFace(face); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(face); useStipple(false); } } } void Renderer::renderRectangle(const Math::Vector3d &originalOrigin, const Math::Vector3d &originalSize, Common::Array *colours, Common::Array *ecolours, float offset) { Math::Vector3d size = originalSize; Math::Vector3d origin = originalOrigin; if (!_isAccelerated) polygonOffset(true); if (size.x() > 0 && size.y() > 0 && size.z() > 0) { /* According to https://www.shdon.com/freescape/ If the bounding box is has all non-zero dimensions and is thus a cube, the rectangle is rendered as a slope at an angle with the plane of the polygon being parallel to the X axis (its lower edge extends from the base corner along the positive X direction). In that case, when the player is at a Z coordinate greater than (i.e. north of) the base corner, it is rendered in the front face material, otherwise it is rendered in the back face material. This implies that the engine does its material selection as though it were a rectangle perpendicular to the Z axis. TODO: fix this case. */ if (size.x() <= size.y() && size.x() <= size.z()) size.x() = 0; else if (size.y() <= size.x() && size.y() <= size.z()) size.y() = 0; else if (size.z() <= size.x() && size.z() <= size.y()) size.z() = 0; else error("Invalid size!"); } float dx, dy, dz; uint8 r1, g1, b1, r2, g2, b2; byte *stipple = nullptr; Common::Array vertices; uint color = 0; uint ecolor = 0; if (size.x() == 0) { origin.x() += offset; } else if (size.y() == 0) { origin.y() += offset; } else if (size.z() == 0) { origin.z() += offset; } for (int i = 0; i < 2; i++) { color = (*colours)[i]; ecolor = ecolours ? (*ecolours)[i] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); vertices.clear(); vertices.push_back(Math::Vector3d(origin.x(), origin.y(), origin.z())); dx = dy = dz = 0.0; if (size.x() == 0) { dy = size.y(); } else if (size.y() == 0) { dx = size.x(); } else if (size.z() == 0) { dx = size.x(); } vertices.push_back(Math::Vector3d(origin.x() + dx, origin.y() + dy, origin.z() + dz)); vertices.push_back(Math::Vector3d(origin.x() + size.x(), origin.y() + size.y(), origin.z() + size.z())); vertices.push_back(Math::Vector3d(origin.x(), origin.y(), origin.z())); dx = dy = dz = 0.0; if (size.x() == 0) { dz = size.z(); } else if (size.y() == 0) { dz = size.z(); } else if (size.z() == 0) { dy = size.y(); } vertices.push_back(Math::Vector3d(origin.x() + dx, origin.y() + dy, origin.z() + dz)); vertices.push_back(Math::Vector3d(origin.x() + size.x(), origin.y() + size.y(), origin.z() + size.z())); renderFace(vertices); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(vertices); useStipple(false); } } } polygonOffset(false); } void Renderer::renderPolygon(const Math::Vector3d &origin, const Math::Vector3d &size, const Common::Array *originalOrdinates, Common::Array *colours, Common::Array *ecolours, float offset) { Common::Array *ordinates = new Common::Array(*originalOrdinates); uint8 r1, g1, b1, r2, g2, b2; byte *stipple = nullptr; if (ordinates->size() % 3 > 0 && ordinates->size() > 0) error("Invalid polygon with size %f %f %f and ordinates %d", size.x(), size.y(), size.z(), ordinates->size()); Common::Array vertices; uint color = 0; uint ecolor = 0; if (ordinates->size() == 6) { // Line polygonOffset(true); color = (*colours)[0]; ecolor = ecolours ? (*ecolours)[0] : 0; assert(getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)); // It will never return false? setStippleData(stipple); useColor(r1, g1, b1); for (uint i = 0; i < ordinates->size(); i = i + 3) vertices.push_back(Math::Vector3d((*ordinates)[i], (*ordinates)[i + 1], (*ordinates)[i + 2])); renderFace(vertices); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(vertices); useStipple(false); } vertices.clear(); color = (*colours)[1]; ecolor = ecolours ? (*ecolours)[1] : 0; assert(getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)); // It will never return false? setStippleData(stipple); useColor(r1, g1, b1); for (int i = ordinates->size(); i > 0; i = i - 3) vertices.push_back(Math::Vector3d((*ordinates)[i - 3], (*ordinates)[i - 2], (*ordinates)[i - 1])); renderFace(vertices); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(vertices); useStipple(false); } polygonOffset(false); } else { if (!_isAccelerated) polygonOffset(true); if (size.x() == 0) { for (int i = 0; i < int(ordinates->size()); i++) { if (i % 3 == 0) (*ordinates)[i] += (offset); } } else if (size.y() == 0) { for (int i = 0; i < int(ordinates->size()); i++) { if (i % 3 == 1) (*ordinates)[i] += (offset); } } else if (size.z() == 0) { for (int i = 0; i < int(ordinates->size()); i++) { if (i % 3 == 2) (*ordinates)[i] += (offset); } } color = (*colours)[0]; ecolor = ecolours ? (*ecolours)[0] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); for (uint i = 0; i < ordinates->size(); i = i + 3) { vertices.push_back(Math::Vector3d((*ordinates)[i], (*ordinates)[i + 1], (*ordinates)[i + 2])); } renderFace(vertices); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(vertices); useStipple(false); } } vertices.clear(); color = (*colours)[1]; ecolor = ecolours ? (*ecolours)[1] : 0; if (getRGBAt(color, ecolor, r1, g1, b1, r2, g2, b2, stipple)) { setStippleData(stipple); useColor(r1, g1, b1); for (int i = ordinates->size(); i > 0; i = i - 3) { vertices.push_back(Math::Vector3d((*ordinates)[i - 3], (*ordinates)[i - 2], (*ordinates)[i - 1])); } renderFace(vertices); if (r1 != r2 || g1 != g2 || b1 != b2) { useStipple(true); useColor(r2, g2, b2); renderFace(vertices); useStipple(false); } } } polygonOffset(false); delete(ordinates); } void Renderer::drawBackground(uint8 color) { uint8 r1, g1, b1; uint8 r2, g2, b2; if (_colorRemaps && _colorRemaps->contains(color)) { color = (*_colorRemaps)[color]; readFromPalette(color, r1, g1, b1); clear(r1, g1, b1); return; } if (color == 0) { clear(0, 0, 0); return; } byte *stipple = nullptr; getRGBAt(color, 0, r1, g1, b1, r2, g2, b2, stipple); clear(r1, g1, b1); } void Renderer::drawEclipse(byte color1, byte color2, float progress) { Math::Vector3d sunPosition(-5000, 1200, 0); float radius = 400.0; drawCelestialBody(sunPosition, radius, color1); Math::Vector3d moonPosition(-5000, 1200, 800 * progress); drawCelestialBody(moonPosition, radius, color2); } Graphics::RendererType determinateRenderType() { Common::String rendererConfig = ConfMan.get("renderer"); Graphics::RendererType desiredRendererType = Graphics::Renderer::parseTypeCode(rendererConfig); Graphics::RendererType matchingRendererType = Graphics::Renderer::getBestMatchingAvailableType(desiredRendererType, #if defined(USE_OPENGL_GAME) Graphics::kRendererTypeOpenGL | #endif #if defined(USE_OPENGL_SHADERS) Graphics::kRendererTypeOpenGLShaders | #endif #if defined(USE_TINYGL) Graphics::kRendererTypeTinyGL | #endif 0); if (matchingRendererType != desiredRendererType && desiredRendererType != Graphics::kRendererTypeDefault) { // Display a warning if unable to use the desired renderer warning("Unable to create a '%s' renderer", rendererConfig.c_str()); } #if defined(USE_OPENGL_GAME) && !defined(USE_GLES2) if (matchingRendererType == Graphics::kRendererTypeOpenGL) return matchingRendererType; #endif #if defined(USE_OPENGL_SHADERS) if (matchingRendererType == Graphics::kRendererTypeOpenGLShaders) return matchingRendererType; #endif #if defined(USE_TINYGL) return Graphics::kRendererTypeTinyGL; #endif return Graphics::kRendererTypeDefault; } Renderer *createRenderer(int screenW, int screenH, Common::RenderMode renderMode, bool authenticGraphics) { Graphics::PixelFormat pixelFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); Graphics::RendererType rendererType = determinateRenderType(); bool isAccelerated = rendererType != Graphics::kRendererTypeTinyGL; if (isAccelerated) { initGraphics3d(screenW, screenH); } else { initGraphics(screenW, screenH, &pixelFormat); } #if defined(USE_OPENGL_GAME) && !defined(USE_GLES2) if (rendererType == Graphics::kRendererTypeOpenGL) { return CreateGfxOpenGL(screenW, screenH, renderMode, authenticGraphics); } #endif #if defined(USE_OPENGL_SHADERS) if (rendererType == Graphics::kRendererTypeOpenGLShaders) { return CreateGfxOpenGLShader(screenW, screenH, renderMode, authenticGraphics); } #endif #if defined(USE_TINYGL) if (rendererType == Graphics::kRendererTypeTinyGL) { // TinyGL graphics are always authentic return CreateGfxTinyGL(screenW, screenH, renderMode); } #endif GUI::MessageDialog dialog("No available renderers enabled"); // TODO: improve message with other renders dialog.runModal(); return nullptr; } } // End of namespace Freescape