From ab2d151541f5d4ae12aeeba6ec5e928109be84f5 Mon Sep 17 00:00:00 2001 From: Bastien Bouclet Date: Mon, 15 Aug 2016 15:16:22 +0200 Subject: MOHAWK: Implement the (fire)flies effect mainly used in jungle island --- engines/mohawk/riven.cpp | 2 + engines/mohawk/riven_card.cpp | 1 + engines/mohawk/riven_external.cpp | 3 +- engines/mohawk/riven_graphics.cpp | 463 ++++++++++++++++++++++++++++++++++++++ engines/mohawk/riven_graphics.h | 103 +++++++++ 5 files changed, 570 insertions(+), 2 deletions(-) diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp index 2536b0246f..485d4bb174 100644 --- a/engines/mohawk/riven.cpp +++ b/engines/mohawk/riven.cpp @@ -204,6 +204,7 @@ void MohawkEngine_Riven::handleEvents() { // Update background running things checkTimer(); _sound->updateSLST(); + _gfx->runFliesEffect(); bool needsUpdate = _gfx->runScheduledWaterEffects(); needsUpdate |= _video->updateMovies(); @@ -495,6 +496,7 @@ void MohawkEngine_Riven::delayAndUpdate(uint32 ms) { while (_system->getMillis() < startTime + ms && !shouldQuit()) { _sound->updateSLST(); + _gfx->runFliesEffect(); bool needsUpdate = _gfx->runScheduledWaterEffects(); needsUpdate |= _video->updateMovies(); diff --git a/engines/mohawk/riven_card.cpp b/engines/mohawk/riven_card.cpp index 4b6feae5ae..894cb4c62a 100644 --- a/engines/mohawk/riven_card.cpp +++ b/engines/mohawk/riven_card.cpp @@ -51,6 +51,7 @@ RivenCard::~RivenCard() { } _vm->_gfx->clearWaterEffects(); + _vm->_gfx->clearFliesEffect(); _vm->_video->stopVideos(); } diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp index c5426dccf7..3855123c3a 100644 --- a/engines/mohawk/riven_external.cpp +++ b/engines/mohawk/riven_external.cpp @@ -2806,8 +2806,7 @@ void RivenExternal::xtatboundary(uint16 argc, uint16 *argv) { // ------------------------------------------------------------------------------------ void RivenExternal::xflies(uint16 argc, uint16 *argv) { - // TODO: Activate the "flies" effect - debug(1, "STUB: xflies(): create %d %s fl%s", argv[1], (argv[0] == 0) ? "black" : "glowing", (argv[1] == 1) ? "y" : "ies"); + _vm->_gfx->setFliesEffect(argv[1], argv[0] == 1); } } // End of namespace Mohawk diff --git a/engines/mohawk/riven_graphics.cpp b/engines/mohawk/riven_graphics.cpp index d5cb3536a1..573eddf7d8 100644 --- a/engines/mohawk/riven_graphics.cpp +++ b/engines/mohawk/riven_graphics.cpp @@ -57,6 +57,7 @@ RivenGraphics::RivenGraphics(MohawkEngine_Riven* vm) : GraphicsManager(), _vm(vm _creditsPos = 0; _transitionSpeed = 0; + _fliesEffect = nullptr; } RivenGraphics::~RivenGraphics() { @@ -65,6 +66,7 @@ RivenGraphics::~RivenGraphics() { _mainScreen->free(); delete _mainScreen; delete _bitmapDecoder; + delete _fliesEffect; } MohawkSurface *RivenGraphics::decodeImage(uint16 id) { @@ -447,4 +449,465 @@ void RivenGraphics::applyScreenUpdate(bool force) { } } +void RivenGraphics::setFliesEffect(uint16 count, bool fireflies) { + delete _fliesEffect; + _fliesEffect = new FliesEffect(_vm, count, fireflies); +} + +void RivenGraphics::clearFliesEffect() { + delete _fliesEffect; + _fliesEffect = nullptr; +} + +void RivenGraphics::runFliesEffect() { + if (_fliesEffect) { + _fliesEffect->update(); + } +} + +Graphics::Surface *RivenGraphics::getBackScreen() { + return _mainScreen; +} + +Graphics::Surface *RivenGraphics::getEffectScreen() { + return _effectScreen; +} + +const FliesEffect::FliesEffectData FliesEffect::_firefliesParameters = { + true, + true, + true, + true, + 3.0, + 0.7, + 40, + 2.0, + 1.0, + 8447718, + 30, + 10 +}; + +const FliesEffect::FliesEffectData FliesEffect::_fliesParameters = { + false, + false, + false, + true, + 8.0, + 3.0, + 80, + 3.0, + 1.0, + 661528, + 30, + 10 +}; + +FliesEffect::FliesEffect(MohawkEngine_Riven *vm, uint16 count, bool fireflies) : + _vm(vm) { + + _effectSurface = _vm->_gfx->getEffectScreen(); + _backSurface = _vm->_gfx->getBackScreen(); + _gameRect = Common::Rect(608, 392); + + if (fireflies) { + _parameters = &_firefliesParameters; + } else { + _parameters = &_fliesParameters; + } + + _updatePeriodMs = 66; + _nextUpdateTime = _vm->_system->getMillis(); + + initFlies(count); +} + +FliesEffect::~FliesEffect() { + +} + +void FliesEffect::initFlies(uint16 count) { + _fly.resize(count); + for (uint16 i = 0; i < _fly.size(); i++) { + initFlyRandomPosition(i); + } +} + +void FliesEffect::initFlyRandomPosition(uint index) { + int posX = _vm->_rnd->getRandomNumber(_gameRect.right - 3); + int posY = _vm->_rnd->getRandomNumber(_gameRect.bottom - 3); + + if (posY < 100) { + posY = 100; + } + + initFlyAtPosition(index, posX, posY, 15); +} + +int FliesEffect::randomBetween(int min, int max) { + return _vm->_rnd->getRandomNumber(max - min) + min; +} + +void FliesEffect::initFlyAtPosition(uint index, int posX, int posY, int posZ) { + FliesEffectEntry &fly = _fly[index]; + + fly.posX = posX; + fly.posXFloat = posX; + fly.posY = posY; + fly.posYFloat = posY; + fly.posZ = posZ; + fly.light = true; + + fly.framesTillLightSwitch = randomBetween(_parameters->minFramesLit, _parameters->minFramesLit + _parameters->maxLightDuration); + + fly.hasBlur = false; + fly.directionAngleRad = randomBetween(0, 300) / 100.0f; + fly.directionAngleRadZ = randomBetween(0, 300) / 100.0f; + fly.speed = randomBetween(0, 100) / 100.0f; +} + +void FliesEffect::update() { + if (_nextUpdateTime <= _vm->_system->getMillis()) { + _nextUpdateTime = _updatePeriodMs + _vm->_system->getMillis(); + + updateFlies(); + draw(); + updateScreen(); + } +} + +void FliesEffect::updateFlies() { + for (uint i = 0; i < _fly.size(); i++) { + updateFlyPosition(i); + + if (_fly[i].posX < 1 || _fly[i].posX > _gameRect.right - 4 || _fly[i].posY > _gameRect.bottom - 4) { + initFlyRandomPosition(i); + } + + if (_parameters->lightable) { + _fly[i].framesTillLightSwitch--; + + if (_fly[i].framesTillLightSwitch <= 0) { + _fly[i].light = !_fly[i].light; + _fly[i].framesTillLightSwitch = randomBetween(_parameters->minFramesLit, _parameters->minFramesLit + _parameters->maxLightDuration); + _fly[i].hasBlur = false; + } + } + } +} + +void FliesEffect::updateFlyPosition(uint index) { + FliesEffectEntry &fly = _fly[index]; + + if (fly.directionAngleRad > 2.0f * M_PI) { + fly.directionAngleRad = fly.directionAngleRad - 2.0f * M_PI; + } + if (fly.directionAngleRad < 0.0f) { + fly.directionAngleRad = fly.directionAngleRad + 2.0f * M_PI; + } + if (fly.directionAngleRadZ > 2.0f * M_PI) { + fly.directionAngleRadZ = fly.directionAngleRadZ - 2.0f * M_PI; + } + if (fly.directionAngleRadZ < 0.0f) { + fly.directionAngleRadZ = fly.directionAngleRadZ + 2.0f * M_PI; + } + fly.posXFloat += cos(fly.directionAngleRad) * fly.speed; + fly.posYFloat += sin(fly.directionAngleRad) * fly.speed; + fly.posX = fly.posXFloat; + fly.posY = fly.posYFloat; + selectAlphaMap( + fly.posXFloat - fly.posX >= 0.5, + fly.posYFloat - fly.posY >= 0.5, + &fly.alphaMap, + &fly.width, + &fly.height); + fly.posZFloat += cos(fly.directionAngleRadZ) * (fly.speed / 2.0f); + fly.posZ = fly.posZFloat; + if (_parameters->canBlur && fly.speed > _parameters->blurSpeedTreshold) { + fly.hasBlur = true; + float blurPosXFloat = cos(fly.directionAngleRad + M_PI) * _parameters->blurDistance + fly.posXFloat; + float blurPosYFloat = sin(fly.directionAngleRad + M_PI) * _parameters->blurDistance + fly.posYFloat; + + fly.blurPosX = blurPosXFloat; + fly.blurPosY = blurPosYFloat; + selectAlphaMap( + blurPosXFloat - fly.blurPosX >= 0.5, + blurPosYFloat - fly.blurPosY >= 0.5, + &fly.blurAlphaMap, + &fly.blurWidth, + &fly.blurHeight); + } + if (fly.posY >= 100) { + int maxAngularSpeed = _parameters->maxAcceleration; + if (fly.posZ > 15) { + maxAngularSpeed /= 2; + } + int angularSpeed = randomBetween(-maxAngularSpeed, maxAngularSpeed); + fly.directionAngleRad += angularSpeed / 100.0f; + } else { + // Make the flies go down if they are too high in the screen + int angularSpeed = randomBetween(0, 50); + if (fly.directionAngleRad >= M_PI / 2.0f && fly.directionAngleRad <= 3.0f * M_PI / 2.0f) { + // Going down + fly.directionAngleRad -= angularSpeed / 100.0f; + } else { + // Going up + fly.directionAngleRad += angularSpeed / 100.0f; + } + if (fly.posY < 1) { + initFlyRandomPosition(index); + } + } + if (fly.posZ >= 0) { + int distanceToScreenEdge; + if (fly.posX / 10 >= (_gameRect.right - fly.posX) / 10) { + distanceToScreenEdge = (_gameRect.right - fly.posX) / 10; + } else { + distanceToScreenEdge = fly.posX / 10; + } + if (distanceToScreenEdge > (_gameRect.bottom - fly.posY) / 10) { + distanceToScreenEdge = (_gameRect.bottom - fly.posY) / 10; + } + if (distanceToScreenEdge > 30) { + distanceToScreenEdge = 30; + } + if (fly.posZ <= distanceToScreenEdge) { + fly.directionAngleRadZ += randomBetween(-_parameters->maxAcceleration, _parameters->maxAcceleration) / 100.0f; + } else { + fly.posZ = distanceToScreenEdge; + fly.directionAngleRadZ += M_PI; + } + } else { + fly.posZ = 0; + fly.directionAngleRadZ += M_PI; + } + float minSpeed = _parameters->minSpeed - fly.posZ / 40.0f; + float maxSpeed = _parameters->maxSpeed - fly.posZ / 20.0f; + fly.speed += randomBetween(-_parameters->maxAcceleration, _parameters->maxAcceleration) / 100.0f; + if (fly.speed > maxSpeed) { + fly.speed -= randomBetween(0, 50) / 100.0f; + } + if (fly.speed < minSpeed) { + fly.speed += randomBetween(0, 50) / 100.0f; + } +} + +void FliesEffect::selectAlphaMap(bool horGridOffset, bool vertGridoffset, const uint16 **alphaMap, uint *width, uint *height) { + static const uint16 alpha1[12] = { + 8, 16, 8, + 16, 32, 16, + 8, 16, 8, + 0, 0, 0 + }; + + static const uint16 alpha2[12] = { + 4, 12, 12, 4, + 8, 24, 24, 8, + 4, 12, 12, 4 + }; + + static const uint16 alpha3[12] = { + 4, 8, 4, + 12, 24, 12, + 12, 24, 12, + 4, 8, 4 + }; + + static const uint16 alpha4[16] = { + 2, 6, 6, 2, + 6, 18, 18, 6, + 6, 18, 18, 6, + 2, 6, 6, 2 + }; + + static const uint16 alpha5[12] = { + 4, 8, 4, + 8, 32, 8, + 4, 8, 4, + 0, 0, 0 + }; + + static const uint16 alpha6[12] = { + 2, 6, 6, 2, + 4, 24, 24, 4, + 2, 6, 6, 2 + }; + + static const uint16 alpha7[12] = { + 2, 4, 2, + 6, 24, 6, + 6, 24, 6, + 2, 4, 2 + }; + + static const uint16 alpha8[16] = { + 1, 3, 3, 1, + 3, 18, 18, 3, + 3, 18, 18, 3, + 1, 3, 3, 1 + }; + + struct AlphaMap { + bool horizontalGridOffset; + bool verticalGridOffset; + bool isLarge; + uint16 width; + uint16 height; + const uint16 *pixels; + }; + + static const AlphaMap alphaSelector[] = { + { true, true, true, 4, 4, alpha4 }, + { true, true, false, 4, 4, alpha8 }, + { true, false, true, 4, 3, alpha2 }, + { true, false, false, 4, 3, alpha6 }, + { false, true, true, 3, 4, alpha3 }, + { false, true, false, 3, 4, alpha7 }, + { false, false, true, 3, 3, alpha1 }, + { false, false, false, 3, 3, alpha5 } + }; + + for (uint i = 0; i < ARRAYSIZE(alphaSelector); i++) { + if (alphaSelector[i].horizontalGridOffset == horGridOffset + && alphaSelector[i].verticalGridOffset == vertGridoffset + && alphaSelector[i].isLarge == _parameters->isLarge) { + *alphaMap = alphaSelector[i].pixels; + *width = alphaSelector[i].width; + *height = alphaSelector[i].height; + return; + } + } + + error("Unknown flies alpha map case"); +} + +void FliesEffect::draw() { + const Graphics::PixelFormat format = _effectSurface->format; + + for (uint i = 0; i < _fly.size(); i++) { + FliesEffectEntry &fly = _fly[i]; + uint32 color = _parameters->color32; + if (!fly.light) { + color = _fliesParameters.color32; + } + + bool hoveringBrightBackground = false; + for (uint y = 0; y < fly.height; y++) { + uint16 *pixel = (uint16 *) _effectSurface->getBasePtr(fly.posX, fly.posY + y); + + for (uint x = 0; x < fly.width; x++) { + byte r, g, b; + format.colorToRGB(*pixel, r, g, b); + + if (_parameters->unlightIfTooBright) { + if (r >= 192 || g >= 192 || b >= 192) { + hoveringBrightBackground = true; + } + } + colorBlending(color, r, g, b, fly.alphaMap[fly.width * y + x] - fly.posZ); + + *pixel = format.RGBToColor(r, g, b); + ++pixel; + } + } + + Common::Rect drawRect = Common::Rect(fly.width, fly.height); + drawRect.translate(fly.posX, fly.posY); + addToScreenDirtyRects(drawRect); + addToEffectsDirtyRects(drawRect); + + if (fly.hasBlur) { + for (uint y = 0; y < fly.blurHeight; y++) { + uint16 *pixel = (uint16 *) _effectSurface->getBasePtr(fly.blurPosX, fly.blurPosY + y); + for (uint x = 0; x < fly.blurWidth; x++) { + byte r, g, b; + format.colorToRGB(*pixel, r, g, b); + + colorBlending(color, r, g, b, fly.blurAlphaMap[fly.blurWidth * y + x] - fly.posZ); + + *pixel = format.RGBToColor(r, g, b); + ++pixel; + } + } + + Common::Rect drawRect2 = Common::Rect(fly.blurWidth, fly.blurHeight); + drawRect2.translate(fly.blurPosX, fly.blurPosY); + addToScreenDirtyRects(drawRect2); + addToEffectsDirtyRects(drawRect2); + + fly.hasBlur = false; + } + + if (hoveringBrightBackground) { + fly.hasBlur = false; + if (_parameters->lightable) { + fly.light = false; + fly.framesTillLightSwitch = randomBetween(_parameters->minFramesLit, _parameters->minFramesLit + _parameters->maxLightDuration); + } + + if (_vm->_rnd->getRandomBit()) { + fly.directionAngleRad += M_PI / 2.0; + } else { + fly.directionAngleRad -= M_PI / 2.0; + } + } + } +} + +void FliesEffect::colorBlending(uint32 flyColor, byte &r, byte &g, byte &b, int alpha) { + alpha = CLIP(alpha, 0, 32); + byte flyR = (flyColor & 0x000000FF) >> 0; + byte flyG = (flyColor & 0x0000FF00) >> 8; + byte flyB = (flyColor & 0x00FF0000) >> 16; + + r = (32 * r + alpha * (flyR - r)) / 32; + g = (32 * g + alpha * (flyG - g)) / 32; + b = (32 * b + alpha * (flyB - b)) / 32; +} + +void FliesEffect::updateScreen() { + for (uint i = 0; i < _screenSurfaceDirtyRects.size(); i++) { + const Common::Rect &rect = _screenSurfaceDirtyRects[i]; + _vm->_system->copyRectToScreen(_effectSurface->getBasePtr(rect.left, rect.top), + _effectSurface->pitch, rect.left, rect.top, + rect.width(), rect.height() + ); + } + _screenSurfaceDirtyRects.clear(); + + restoreEffectsSurface(); +} + +void FliesEffect::addToScreenDirtyRects(const Common::Rect &rect) { + for (uint i = 0; i < _screenSurfaceDirtyRects.size(); i++) { + if (rect.intersects(_screenSurfaceDirtyRects[i])) { + _screenSurfaceDirtyRects[i].extend(rect); + return; + } + } + + _screenSurfaceDirtyRects.push_back(rect); +} + +void FliesEffect::addToEffectsDirtyRects(const Common::Rect &rect) { + for (uint i = 0; i < _effectsSurfaceDirtyRects.size(); i++) { + if (rect.intersects(_effectsSurfaceDirtyRects[i])) { + _effectsSurfaceDirtyRects[i].extend(rect); + return; + } + } + + _effectsSurfaceDirtyRects.push_back(rect); +} + +void FliesEffect::restoreEffectsSurface() { + for (uint i = 0; i < _effectsSurfaceDirtyRects.size(); i++) { + const Common::Rect &rect = _effectsSurfaceDirtyRects[i]; + _effectSurface->copyRectToSurface(*_backSurface, rect.left, rect.top, rect); + addToScreenDirtyRects(rect); + } + + _effectsSurfaceDirtyRects.clear(); +} + } // End of namespace Mohawk diff --git a/engines/mohawk/riven_graphics.h b/engines/mohawk/riven_graphics.h index 2802c8402e..8120879310 100644 --- a/engines/mohawk/riven_graphics.h +++ b/engines/mohawk/riven_graphics.h @@ -28,6 +28,7 @@ namespace Mohawk { class MohawkEngine_Riven; +class FliesEffect; class RivenGraphics : public GraphicsManager { public: @@ -44,11 +45,19 @@ public: void drawImageRect(uint16 id, Common::Rect srcRect, Common::Rect dstRect); void drawExtrasImage(uint16 id, Common::Rect dstRect); + Graphics::Surface *getEffectScreen(); + Graphics::Surface *getBackScreen(); + // Water Effect void scheduleWaterEffect(uint16); void clearWaterEffects(); bool runScheduledWaterEffects(); + // Flies Effect + void setFliesEffect(uint16 count, bool fireflies); + void clearFliesEffect(); + void runFliesEffect(); + // Transitions void scheduleTransition(uint16 id, Common::Rect rect = Common::Rect(0, 0, 608, 392)); void runScheduledTransition(); @@ -88,6 +97,9 @@ private: }; Common::Array _waterEffects; + // Flies Effect + FliesEffect *_fliesEffect; + // Transitions int16 _scheduledTransition; Common::Rect _transitionRect; @@ -102,6 +114,7 @@ private: Graphics::Surface *_mainScreen; Graphics::Surface *_effectScreen; bool _dirtyScreen; + Graphics::PixelFormat _pixelFormat; void clearMainScreen(); @@ -109,6 +122,96 @@ private: uint _creditsImage, _creditsPos; }; +/** + * The flies effect draws flies in the scene + * + * It can draw either regular flies or fireflies. + * The flies' movement is simulated in 3 dimensions. + */ +class FliesEffect { +public: + FliesEffect(MohawkEngine_Riven *vm, uint16 count, bool fireflies); + ~FliesEffect(); + + /** Simulate the flies' movement and draw them to the screen */ + void update(); + +private: + struct FliesEffectEntry { + bool light; + int posX; + int posY; + int posZ; + const uint16 *alphaMap; + uint width; + uint height; + int framesTillLightSwitch; + bool hasBlur; + int blurPosX; + int blurPosY; + const uint16 *blurAlphaMap; + uint blurWidth; + uint blurHeight; + float posXFloat; + float posYFloat; + float posZFloat; + float directionAngleRad; + float directionAngleRadZ; + float speed; + }; + + struct FliesEffectData { + bool lightable; + bool unlightIfTooBright; + bool isLarge; + bool canBlur; + float maxSpeed; + float minSpeed; + int maxAcceleration; + float blurSpeedTreshold; + float blurDistance; + uint32 color32; + int minFramesLit; + int maxLightDuration; + }; + + MohawkEngine_Riven *_vm; + + uint _nextUpdateTime; + int _updatePeriodMs; + + Common::Rect _gameRect; + Graphics::Surface *_effectSurface; + Graphics::Surface *_backSurface; + Common::Array _screenSurfaceDirtyRects; + Common::Array _effectsSurfaceDirtyRects; + + const FliesEffectData *_parameters; + static const FliesEffectData _firefliesParameters; + static const FliesEffectData _fliesParameters; + + Common::Array _fly; + + void initFlies(uint16 count); + void initFlyRandomPosition(uint index); + void initFlyAtPosition(uint index, int posX, int posY, int posZ); + + void updateFlies(); + void updateFlyPosition(uint index); + + void draw(); + void updateScreen(); + + void selectAlphaMap(bool horGridOffset, bool vertGridoffset, const uint16 **alphaMap, uint *width, uint *height); + void colorBlending(uint32 flyColor, byte &r, byte &g, byte &b, int alpha); + + void addToScreenDirtyRects(const Common::Rect &rect); + void addToEffectsDirtyRects(const Common::Rect &rect); + void restoreEffectsSurface(); + + int randomBetween(int min, int max); +}; + } // End of namespace Mohawk #endif -- cgit v1.2.3