scummvm/engines/myst3/effects.cpp
Bastien Bouclet f94b3d74a1 MYST3: Implement the shake effect
Visible when inside Amateria's magnetic rings
2014-02-23 17:52:45 +01:00

363 lines
8.9 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the AUTHORS
* 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 "engines/myst3/effects.h"
#include "engines/myst3/myst3.h"
#include "engines/myst3/state.h"
namespace Myst3 {
Effect::Effect(Myst3Engine *vm) :
_vm(vm) {
}
Effect::~Effect() {
for (FaceMaskMap::iterator it = _facesMasks.begin(); it != _facesMasks.end(); it++) {
it->_value->free();
delete it->_value;
}
}
bool Effect::loadMasks(uint32 id, DirectorySubEntry::ResourceType type) {
// Just in case
_facesMasks.clear();
bool isFrame = _vm->_state->getViewType() == kFrame;
// Load the mask of each face
for (uint i = 0; i < 6; i++) {
const DirectorySubEntry *desc = _vm->getFileDescription(0, id, i + 1, type);
if (desc) {
Common::SeekableReadStream *data = desc->getData();
_facesMasks[i] = loadMask(data);
// Frame masks are vertically flipped for some reason
if (isFrame) {
flipVertical(_facesMasks[i]);
}
delete data;
}
}
if (_facesMasks.empty())
return false;
return true;
}
Graphics::Surface *Effect::loadMask(Common::SeekableReadStream *maskStream) {
Graphics::Surface *s = new Graphics::Surface();
s->create(640, 640, Graphics::PixelFormat::createFormatCLUT8());
uint32 headerOffset = 0;
uint32 dataOffset = 0;
while (headerOffset < 400) {
int blockX = (headerOffset / sizeof(dataOffset)) % 10;
int blockY = (headerOffset / sizeof(dataOffset)) / 10;
maskStream->seek(headerOffset, SEEK_SET);
dataOffset = maskStream->readUint32LE();
headerOffset = maskStream->pos();
if (dataOffset != 0) {
maskStream->seek(dataOffset, SEEK_SET);
for(int i = 63; i >= 0; i--) {
int x = 0;
byte numValues = maskStream->readByte();
for (int j = 0; j < numValues; j++) {
byte repeat = maskStream->readByte();
byte value = maskStream->readByte();
for (int k = 0; k < repeat; k++) {
((uint8*)s->getPixels())[((blockY * 64) + i) * 640 + blockX * 64 + x] = value;
x++;
}
}
}
}
}
return s;
}
void Effect::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->w; ++x)
SWAP(line1P[x], line2P[x]);
}
}
WaterEffect::WaterEffect(Myst3Engine *vm) :
Effect(vm),
_lastUpdate(0),
_step(0) {
}
WaterEffect::~WaterEffect() {
}
WaterEffect *WaterEffect::create(Myst3Engine *vm, uint32 id) {
WaterEffect *s = new WaterEffect(vm);
if (!s->loadMasks(id, DirectorySubEntry::kWaterEffectMask)) {
delete s;
return 0;
}
return s;
}
bool WaterEffect::isRunning() {
return _vm->_state->getWaterEffectActive()
&& _vm->_state->getWaterEffectRunning();
}
bool WaterEffect::update() {
if (!isRunning()) {
return false;
}
if (g_system->getMillis() - _lastUpdate >= 1000 / (uint32)_vm->_state->getWaterEffectSpeed()) {
_lastUpdate = g_system->getMillis();
_step++;
if (_step > _vm->_state->getWaterEffectMaxStep())
_step = 0;
float position = _step / (float)_vm->_state->getWaterEffectMaxStep();
doStep(position, _vm->_state->getViewType() == kFrame);
return true;
}
return false;
}
void WaterEffect::doStep(float position, bool isFrame) {
double timeOffset;
double frequency;
double ampl;
timeOffset = position * 2 * M_PI;
frequency = _vm->_state->getWaterEffectFrequency() * 0.1;
ampl = _vm->_state->getWaterEffectAmpl() / 10.0 / 2.0;
for (uint i = 0; i < 640; i++) {
double ampl1;
if (i < 320)
ampl1 = i / 320 + 1.0;
else
ampl1 = (640 - i) / 320 + 1.0;
_bottomDisplacement[i] = sin(i / 640.0 * frequency * 2 * M_PI + timeOffset) / 2 * ampl1 * ampl;
}
// FIXME: The original sets this to WaterEffectAttenuation, which causes
// glitches here
uint32 attenuation = 640;
for (uint i = 0; i < attenuation; i++) {
double ampl2 = attenuation / (attenuation - i + 1.0);
int8 value = sin(i / 640.0 * frequency * 2 * M_PI * ampl2 + timeOffset) / 2 * 1.0 / ampl2 * ampl;
if (!isFrame) {
_verticalDisplacement[i] = value;
} else {
_verticalDisplacement[attenuation - 1 - i] = value;
}
}
for (uint i = 0; i < 640; i++) {
double ampl3 = sin(i / 640.0 * frequency * 2 * M_PI + timeOffset) / 2.0;
_horizontalDisplacements[0][i] = ampl3 * 1.25 * ampl + 0.5;
_horizontalDisplacements[1][i] = ampl3 * 1.00 * ampl + 0.5;
_horizontalDisplacements[2][i] = ampl3 * 0.75 * ampl + 0.5;
_horizontalDisplacements[3][i] = ampl3 * 0.50 * ampl + 0.5;
_horizontalDisplacements[4][i] = ampl3 * 0.25 * ampl + 0.5;
}
}
void WaterEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
if (!isRunning()) {
return;
}
Graphics::Surface *mask = _facesMasks.getVal(face);
if (!mask)
error("No mask for face %d", face);
apply(src, dst, mask, face == 1, _vm->_state->getWaterEffectAmpl());
}
void WaterEffect::apply(Graphics::Surface *src, Graphics::Surface *dst, Graphics::Surface *mask, bool bottomFace, int32 waterEffectAmpl) {
int8 *hDisplacement;
int8 *vDisplacement;
if (bottomFace) {
hDisplacement = _bottomDisplacement;
vDisplacement = _bottomDisplacement;
} else {
vDisplacement = _verticalDisplacement;
}
uint32 *dstPtr = (uint32 *)dst->getPixels();
byte *maskPtr = (byte *)mask->getPixels();
for (uint y = 0; y < dst->h; y++) {
if (!bottomFace) {
uint32 strength = (320 * (9 - y / 64)) / _vm->_state->getWaterEffectAttenuation();
if (strength > 4)
strength = 4;
hDisplacement = _horizontalDisplacements[strength];
}
for (uint x = 0; x < dst->w; x++) {
int8 maskValue = *maskPtr;
if (maskValue != 0) {
int8 xOffset = hDisplacement[x];
int8 yOffset = vDisplacement[y];
if (maskValue < 8) {
maskValue -= _vm->_state->getWaterEffectAmplOffset();
if (maskValue < 0) {
maskValue = 0;
}
if (xOffset >= 0) {
if (xOffset > maskValue)
xOffset = maskValue;
} else {
if (-xOffset > maskValue)
xOffset = -maskValue;
}
if (yOffset >= 0) {
if (yOffset > maskValue)
yOffset = maskValue;
} else {
if (-yOffset > maskValue)
yOffset = -maskValue;
}
}
uint32 srcValue1 = *(uint32 *) src->getBasePtr(x + xOffset, y + yOffset);
uint32 srcValue2 = *(uint32 *) src->getBasePtr(x, y);
*dstPtr = 0xFF000000 | ((0x007F7F7F & (srcValue1 >> 1)) + (0x007F7F7F & (srcValue2 >> 1)));
}
maskPtr++;
dstPtr++;
}
}
}
ShakeEffect::ShakeEffect(Myst3Engine *vm) :
Effect(vm),
_lastFrame(0),
_magnetEffectShakeStep(0),
_pitchOffset(0),
_headingOffset(0) {
}
ShakeEffect::~ShakeEffect() {
}
ShakeEffect *ShakeEffect::create(Myst3Engine *vm) {
if (vm->_state->getShakeEffectAmpl() == 0) {
return nullptr;
}
return new ShakeEffect(vm);
}
bool ShakeEffect::update() {
// Check if the effect is active
uint32 ampl = _vm->_state->getShakeEffectAmpl();
if (ampl == 0) {
return false;
}
// Check if the effect needs to be updated
uint frame = _vm->_state->getFrameCount();
if (frame < _lastFrame + _vm->_state->getShakeEffectFramePeriod()) {
return false;
}
if (_vm->_state->getMagnetEffectUnk3()) {
// If the magnet effect is also active, use its parameters
float magnetEffectAmpl = (_vm->_state->getMagnetEffectUnk1() + _vm->_state->getMagnetEffectUnk3()) / 32.0;
float shakeEffectAmpl;
if (_magnetEffectShakeStep >= 2) {
shakeEffectAmpl = ampl;
} else {
shakeEffectAmpl = -ampl;
}
_pitchOffset = shakeEffectAmpl / 200.0 * magnetEffectAmpl;
if (_magnetEffectShakeStep >= 1 && _magnetEffectShakeStep <= 2) {
shakeEffectAmpl = ampl;
} else {
shakeEffectAmpl = -ampl;
}
_headingOffset = shakeEffectAmpl / 200.0 * magnetEffectAmpl;
_magnetEffectShakeStep++;
_magnetEffectShakeStep %= 3;
} else {
// Shake effect only
uint randomAmpl;
randomAmpl = _vm->_rnd->getRandomNumberRng(0, ampl);
_pitchOffset = (randomAmpl - ampl / 2.0) / 100.0;
randomAmpl = _vm->_rnd->getRandomNumberRng(0, ampl);
_headingOffset = (randomAmpl - ampl / 2.0) / 100.0;
}
_lastFrame = frame;
return true;
}
void ShakeEffect::applyForFace(uint face, Graphics::Surface* src,
Graphics::Surface* dst) {
}
} /* namespace Myst3 */