/* 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 "watchmaker/3d/movie.h" #include "watchmaker/3d/dds_header.h" #include "watchmaker/file_utils.h" #include "watchmaker/windows_hacks.h" #include "watchmaker/work_dirs.h" namespace Watchmaker { gMovie::gMovie(Common::SharedPtr stream, Texture *texture, const Common::String &name) : _name(name), _stream(stream), _texture(texture) { _numFrames = stream->readUint16LE(); _width = stream->readUint16LE(); _height = stream->readUint16LE(); _keyFrame = stream->readByte(); _frameRate = stream->readByte(); _header = DDSHeader(*stream); _numBlocks = _width * _height / 16; _curFrame = 0xFFFF; _frameOffsets = new uint32[_numFrames] {}; if (!_frameOffsets) { error("gLoadMovie FAILED: Can't alloc Movie->frameOffsets struct"); } _buffer = new uint8[bufferSize()] {}; _surfaceBuffer = new uint8[_header.dataSize()] {}; _frameStream = new Common::MemoryReadStream(_surfaceBuffer, _header.dataSize(), DisposeAfterUse::NO); if (!_buffer) { error("gLoadMovie FAILED: Can't alloc Movie->buffer struct"); } //read frame offsets for (int i = 0; i < _numFrames; i++) { _frameOffsets[i] = _stream->readUint32LE(); } _startTime = 0; } gMovie::~gMovie() { delete[] _frameOffsets; delete[] _buffer; delete[] _surfaceBuffer; delete _frameStream; } Common::SharedPtr gLoadMovie(WorkDirs &workDirs, const char *TextName, Texture *texture) { //convert .avi name in .wmm Common::String finalName = replaceExtension(TextName, "wmm"); auto stream = workDirs.resolveFile(finalName); if (!stream) { DebugLogFile("gLoadMovie FAILED: Can't find movie file\n"); return nullptr; } auto Movie = Common::SharedPtr(new gMovie(stream, texture, TextName)); Movie->_name = TextName; if (!Movie) { DebugLogFile("gLoadMovie FAILED: Can't alloc Movie struct"); return nullptr; } // Movie->frameRate=240; return Movie; } void gMovie::loadThisFrameData(uint16 frame) { _stream->seek(_frameOffsets[frame], SEEK_SET); //read frame data int32 size = 0; if ((frame + 1) == _numFrames) { size = _stream->size() - _frameOffsets[frame]; } else { size = _frameOffsets[frame + 1] - _frameOffsets[frame]; } assert(size <= (int32)bufferSize()); _stream->read(_buffer, size); } //build a new frame by difference from previous void gMovie::buildNewFrame(byte *surf, uint16 frame) { loadThisFrameData(frame); DWORD bitArraySize = _numBlocks >> 3; byte *buf = &_buffer[bitArraySize]; WORD curBlock = 0; for (int i = 0; i < bitArraySize; i++) { byte block = _buffer[i]; if (!block) { curBlock += 8; continue; //everything is equal } for (int j = 0; j < 8; j++, curBlock++) { if (block & (1 << j)) { memcpy(&surf[curBlock << 3], buf, 8); buf += 8; } } } } bool gMovie::setFrame(uint16 newFrame) { warning("Set Frame: %d\t%s", newFrame, _name.c_str()); if (_curFrame == newFrame) return true; //do we have to replace the whole frame or do we have to built it? bool rebuildFrame = true; if (_curFrame == 0xFFFF) rebuildFrame = false; else if (!(newFrame % _keyFrame)) rebuildFrame = false; //it's a keyframe _header.dataSize(); #if 0 DDSURFACEDESC2 ddsd2; ddsd2.dwSize = sizeof(DDSURFACEDESC2); if ((mv->surf->Lock(NULL, &ddsd2, DDLOCK_NOSYSLOCK | DDLOCK_WAIT, NULL))) { // Lock and fill with the dds DebugLogFile("gMovie_SetFrame: Can't lock surface DDS"); return NULL; } #endif if (!rebuildFrame) { loadThisFrameData(newFrame); memcpy(_surfaceBuffer, _buffer, _header.dataSize()); } else { if ((_curFrame + 1) != newFrame) { //we can't directly build this frame because the current frame is not his previous uint16 startFrame; uint16 prevKey = (newFrame / _keyFrame) * _keyFrame; if ((_curFrame > newFrame) || (_curFrame < prevKey)) { loadThisFrameData(prevKey); memcpy(_surfaceBuffer, _buffer, _header.dataSize()); startFrame = prevKey + 1; } else startFrame = _curFrame + 1; for (uint16 i = startFrame; i < newFrame; i++) { buildNewFrame(_surfaceBuffer, i); } } buildNewFrame(_surfaceBuffer, newFrame); } _frameStream->seek(0, SEEK_SET); auto tex = loadDdsTexture(*_frameStream, _header); _texture->assignData(*tex); #if 0 if (mat->Texture->lpDDSurface->Blt(NULL, mv->surf, NULL, DDBLT_WAIT, NULL) != DD_OK) { DebugLogFile("gMovie_SetFrame: Can't Blit DDS texture"); return NULL; } #endif _curFrame = newFrame; return true; } //********************************************************************************************* bool gMovie::updateMovie() { int16 newFrame = 0; if (_paused) return TRUE; warning("Update Movie: %s", _name.c_str()); if ((_curFrame == 0xFFFF) || (!_startTime)) { _startTime = timeGetTime(); newFrame = 0; } else { // Use the time to find which frame we should be drawing uint32 curTime = timeGetTime(); DWORD elapsedTime = curTime - _startTime; newFrame = (WORD)((float)elapsedTime / (1000.f / (float)_frameRate)); if (newFrame >= _numFrames) { _startTime = curTime; newFrame = 0; } } return setFrame(newFrame); } int gMovie::frameSize(int index) { if ((index + 1) < _numFrames) { return _frameOffsets[index + 1] - _frameOffsets[index]; } else { return _stream->size() - _frameOffsets[index]; } } uint32 gMovie::bufferSize() const { return (_numBlocks / 8) + 8 * _numBlocks; //bit array + max different blocks } } // End of namespace Watchmaker