diff options
Diffstat (limited to 'sword2/driver/animation.cpp')
| -rw-r--r-- | sword2/driver/animation.cpp | 388 | 
1 files changed, 341 insertions, 47 deletions
diff --git a/sword2/driver/animation.cpp b/sword2/driver/animation.cpp index 365457be25..74c681de8a 100644 --- a/sword2/driver/animation.cpp +++ b/sword2/driver/animation.cpp @@ -28,11 +28,13 @@  namespace Sword2 { -// Build 'Best-Match' RGB lookup table +/** + * Build 'Best-Match' RGB lookup table + */ +  void AnimationState::buildLookup(int p, int lines) {  	int y, cb;  	int r, g, b, ii; -  	if (p != curpal) {  		curpal = p; @@ -44,17 +46,17 @@ void AnimationState::buildLookup(int p, int lines) {  		return;  	for (ii = 0; ii < lines; ii++) { -		r = (-16*256 + (int)(256*1.596) * ((cr<<SHIFT)-128)) / 256; +		r = (-16 * 256 + (int) (256 * 1.596) * ((cr << SHIFT) - 128)) / 256;  		for (cb = 0; cb < BITDEPTH; cb++) { -			g = (-16*256 - (int)(0.813*256) * ((cr<<SHIFT)-128) - (int)(0.391*256) * ((cb<<SHIFT)-128)) / 256; -			b = (-16*256 + (int)(2.018*256) * ((cb<<SHIFT)-128)) / 256; +			g = (-16 * 256 - (int) (0.813 * 256) * ((cr << SHIFT) - 128) - (int) (0.391 * 256) * ((cb << SHIFT) - 128)) / 256; +			b = (-16 * 256 + (int) (2.018 * 256) * ((cb << SHIFT) - 128)) / 256;  			for (y = 0; y < BITDEPTH; y++) {  				int idx, bst = 0; -				int dis = 2*SQR(r-palettes[p].pal[0])+4*SQR(g-palettes[p].pal[1])+SQR(b-palettes[p].pal[2]); +				int dis = 2 * SQR(r - palettes[p].pal[0]) + 4 * SQR(g - palettes[p].pal[1]) + SQR(b - palettes[p].pal[2]);  				for (idx = 1; idx < 256; idx++) { -					long d2 = 2*SQR(r-palettes[p].pal[4*idx])+4*SQR(g-palettes[p].pal[4*idx+1])+SQR(b-palettes[p].pal[4*idx+2]); +					long d2 = 2 * SQR(r - palettes[p].pal[4 * idx]) + 4 * SQR(g - palettes[p].pal[4 * idx + 1]) + SQR(b - palettes[p].pal[4 * idx + 2]);  					if (d2 < dis) {  						bst = idx;  						dis = d2; @@ -74,6 +76,294 @@ void AnimationState::buildLookup(int p, int lines) {  	}  } +void MoviePlayer::openTextObject(MovieTextObject *obj) { +	if (obj->textSprite) +		_vm->_graphics->createSurface(obj->textSprite, &_textSurface); +} + +void MoviePlayer::closeTextObject(MovieTextObject *obj) { +	if (_textSurface) { +		_vm->_graphics->deleteSurface(_textSurface); +		_textSurface = NULL; +	} +} + +void MoviePlayer::drawTextObject(MovieTextObject *obj) { +	if (obj->textSprite && _textSurface) +		_vm->_graphics->drawSurface(obj->textSprite, _textSurface); +} + +/** + * Plays an animated cutscene. + * @param filename the file name of the cutscene file + * @param text the subtitles and voiceovers for the cutscene + * @param musicOut lead-out music + */ + +int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], uint8 *musicOut) { +#ifdef USE_MPEG2 +	int frameCounter = 0, textCounter = 0; +	PlayingSoundHandle handle; +	bool skipCutscene = false, textVisible = false; +	uint32 flags = SoundMixer::FLAG_16BITS, ticks = _vm->_system->get_msecs() + 83; + +	uint8 oldPal[1024]; +	memcpy(oldPal, _vm->_graphics->_palCopy, 1024); + +	AnimationState * anim = initAnimation(filename); +	if (!anim) { +		// Missing Files? Use the old 'Narration Only' hack +		playDummy(filename, text, musicOut); +		return RD_OK; +	} + +	_vm->_graphics->clearScene(); +	memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); + +#ifndef SCUMM_BIG_ENDIAN +	flags |= SoundMixer::FLAG_LITTLE_ENDIAN; +#endif + +	while (1) { +		if (!decodeFrame(anim)) break; +		_vm->_graphics->setNeedFullRedraw(); + +		if (text && text[textCounter]) {                       +			if (frameCounter == text[textCounter]->startFrame) { +				openTextObject(text[textCounter]); +				textVisible = true; +				if (text[textCounter]->speech) { +					_vm->_mixer->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags); +				} +			} + +			if (frameCounter == text[textCounter]->endFrame) { +				closeTextObject(text[textCounter]); +				textCounter++; +				textVisible = false; +			} +			if (textVisible) +				drawTextObject(text[textCounter]); +		} + +		frameCounter++; + +		_vm->_graphics->updateDisplay(true); + +		KeyboardEvent ke; + +		if ((_vm->_input->readKey(&ke) == RD_OK && ke.keycode == 27) || _vm->_quit) { +			_vm->_mixer->stopHandle(handle); +			skipCutscene = true; +			break; +		} + +		// Simulate ~12 frames per second. I don't know what +		// frame rate the original movies had, or even if it +		// was constant, but this seems to work reasonably. + +		_vm->sleepUntil(ticks); +		ticks += 82; + +	} + +	// Wait for the voice to stop playing. This is to make sure +	// that we don't cut off the speech in mid-sentence, and - even +	// more importantly - that we don't free the sound buffer while +	// it's in use. + +	while (handle.isActive()) { +		_vm->_graphics->updateDisplay(false); +		_vm->_system->delay_msecs(100); +	} + +	if (text) +		closeTextObject(text[textCounter]); + +	_vm->_graphics->clearScene(); +	_vm->_graphics->setNeedFullRedraw(); + +	// HACK: Remove the instructions created above +	Common::Rect r; + +	memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); +	r.left = r.top = 0; +	r.right = _vm->_graphics->_screenWide; +	r.bottom = MENUDEEP; +	_vm->_graphics->updateRect(&r); + +	// FIXME: For now, only play the lead-out music for cutscenes +	// that have subtitles. +	if (!skipCutscene) +		_vm->_sound->playLeadOut(musicOut); + +	_vm->_graphics->setPalette(0, 256, oldPal, RDPAL_INSTANT); + +	doneAnimation(anim); + +	// Lead-in and lead-out music are, as far as I can tell, only used for +	// the animated cut-scenes, so this seems like a good place to close +	// both of them. + +	_vm->_sound->closeFx(-1); +	_vm->_sound->closeFx(-2); + +	return RD_OK; +#else +	// No MPEG2? Use the old 'Narration Only' hack +	playDummy(filename, text, musicOut); +	return RD_OK; +#endif +} + +/** + * This just plays the cutscene with voiceovers / subtitles, in case the files + * are missing. + */ + +int32 MoviePlayer::playDummy(const char *filename, MovieTextObject *text[], uint8 *musicOut) { +	int frameCounter = 0, textCounter = 0; +	if (text) { +		uint8 oldPal[1024]; +		uint8 tmpPal[1024]; + +		_vm->_graphics->clearScene(); + +		// HACK: Draw instructions +		// +		// I'm using the the menu area, because that's unlikely to be +		// touched by anything else during the cutscene. + +		memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); + +		uint8 msg[] = "Cutscene - Narration Only: Press ESC to exit, or visit www.scummvm.org to download cutscene videos"; +		Memory *data = _vm->_fontRenderer->makeTextSprite(msg, 640, 255, _vm->_speechFontId); +		FrameHeader *frame = (FrameHeader *) data->ad; +		SpriteInfo msgSprite; +		uint8 *msgSurface; + +		msgSprite.x = _vm->_graphics->_screenWide / 2 - frame->width / 2; +		msgSprite.y = RDMENU_MENUDEEP / 2 - frame->height / 2; +		msgSprite.w = frame->width; +		msgSprite.h = frame->height; +		msgSprite.type = RDSPR_NOCOMPRESSION; +		msgSprite.data = data->ad + sizeof(FrameHeader); + +		_vm->_graphics->createSurface(&msgSprite, &msgSurface); +		_vm->_graphics->drawSurface(&msgSprite, msgSurface); +		_vm->_graphics->deleteSurface(msgSurface); +		_vm->_memory->freeMemory(data); + +		// In case the cutscene has a long lead-in, start just before +		// the first line of text. + +		frameCounter = text[0]->startFrame - 12; + +		// Fake a palette that will hopefully make the text visible. +		// In the opening cutscene it seems to use colours 1 (black?) +		// and 255 (white?). +		// +		// The text should probably be colored the same as the rest of +		// the in-game text. + +		memcpy(oldPal, _vm->_graphics->_palCopy, 1024); +		memset(tmpPal, 0, 1024); +		tmpPal[255 * 4 + 0] = 255; +		tmpPal[255 * 4 + 1] = 255; +		tmpPal[255 * 4 + 2] = 255; +		_vm->_graphics->setPalette(0, 256, tmpPal, RDPAL_INSTANT); + +		PlayingSoundHandle handle; + +		bool skipCutscene = false; + +		uint32 flags = SoundMixer::FLAG_16BITS; + +#ifndef SCUMM_BIG_ENDIAN +		flags |= SoundMixer::FLAG_LITTLE_ENDIAN; +#endif + +		while (1) { +			if (!text[textCounter]) +				break; + +			if (frameCounter == text[textCounter]->startFrame) { +				_vm->_graphics->clearScene(); +				openTextObject(text[textCounter]); +				drawTextObject(text[textCounter]); +				if (text[textCounter]->speech) { +					_vm->_mixer->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags); +				} +			} +			if (frameCounter == text[textCounter]->endFrame) { +				closeTextObject(text[textCounter]); +				_vm->_graphics->clearScene(); +				_vm->_graphics->setNeedFullRedraw(); +				textCounter++; +			} + +			frameCounter++; + +			_vm->_graphics->updateDisplay(); + +			KeyboardEvent ke; + +			if ((_vm->_input->readKey(&ke) == RD_OK && ke.keycode == 27) || _vm->_quit) { +				_vm->_mixer->stopHandle(handle); +				skipCutscene = true; +				break; +			} + +			// Simulate ~12 frames per second. I don't know what +			// frame rate the original movies had, or even if it +			// was constant, but this seems to work reasonably. + +			_vm->_system->delay_msecs(90); +		} + +		// Wait for the voice to stop playing. This is to make sure +		// that we don't cut off the speech in mid-sentence, and - even +		// more importantly - that we don't free the sound buffer while +		// it's in use. + +		while (handle.isActive()) { +			_vm->_graphics->updateDisplay(false); +			_vm->_system->delay_msecs(100); +		} + +		closeTextObject(text[textCounter]); + +		_vm->_graphics->clearScene(); +		_vm->_graphics->setNeedFullRedraw(); + +		// HACK: Remove the instructions created above +		Common::Rect r; + +		memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); +		r.left = r.top = 0; +		r.right = _vm->_graphics->_screenWide; +		r.bottom = MENUDEEP; +		_vm->_graphics->updateRect(&r); + +		// FIXME: For now, only play the lead-out music for cutscenes +		// that have subtitles. + +		if (!skipCutscene) +			_vm->_sound->playLeadOut(musicOut); + +		_vm->_graphics->setPalette(0, 256, oldPal, RDPAL_INSTANT); +	} + +	// Lead-in and lead-out music are, as far as I can tell, only used for +	// the animated cut-scenes, so this seems like a good place to close +	// both of them. + +	_vm->_sound->closeFx(-1); +	_vm->_sound->closeFx(-2); + +	return RD_OK; +} +  void MoviePlayer::checkPaletteSwitch(AnimationState * st) {  	// if we have reached the last image with this palette, switch to new one  	if (st->framenum == st->palettes[st->palnum].end) { @@ -86,52 +376,42 @@ void MoviePlayer::checkPaletteSwitch(AnimationState * st) {  	}  } -#ifndef USE_MPEG2 -bool MoviePlayer::decodeFrame(AnimationState * st) { -	// Dummy for MPEG2-less builds -	return false; -} -#else +#ifdef USE_MPEG2 +  bool MoviePlayer::decodeFrame(AnimationState * st) {  	mpeg2_state_t state;  	const mpeg2_sequence_t *sequence_i; -	size_t size = (size_t)-1; +	size_t size = (size_t) -1;  	do { -		state = mpeg2_parse (st->decoder); +		state = mpeg2_parse(st->decoder);  		sequence_i = st->info->sequence;  		switch (state) { -			case STATE_BUFFER: -				size = st->mpgfile->read(st->buffer, BUFFER_SIZE); -				mpeg2_buffer (st->decoder, st->buffer, st->buffer + size); +		case STATE_BUFFER: +			size = st->mpgfile->read(st->buffer, BUFFER_SIZE); +			mpeg2_buffer(st->decoder, st->buffer, st->buffer + size);  			break; -			case STATE_SLICE: -			case STATE_END: -				if (st->info->display_fbuf) { -					checkPaletteSwitch(st); -					_vm->_graphics->plotYUV(st->lut, sequence_i->width, sequence_i->height, st->info->display_fbuf->buf); -					st->framenum++; -					st->buildLookup(st->palnum+1, st->lutcalcnum); -					return true; -				} +		case STATE_SLICE: +		case STATE_END: +			if (st->info->display_fbuf) { +				checkPaletteSwitch(st); +				_vm->_graphics->plotYUV(st->lut, sequence_i->width, sequence_i->height, st->info->display_fbuf->buf); +				st->framenum++; +				st->buildLookup(st->palnum + 1, st->lutcalcnum); +				return true; +			}  			break; -			default: -				break; +		default: +			break;  		}  	} while (size);  	return false;  } -#endif -#ifndef USE_MPEG2 -AnimationState *MoviePlayer::initAnimation(const char *name) { -	return 0; -} -#else  AnimationState *MoviePlayer::initAnimation(const char *name) {  	char basename[512], tempFile[512];  	AnimationState *st = new AnimationState; @@ -158,9 +438,9 @@ AnimationState *MoviePlayer::initAnimation(const char *name) {    			fscanf(f, "%i", &r);    			fscanf(f, "%i", &g);    			fscanf(f, "%i", &b); -			st->palettes[p].pal[4*i] = r; -			st->palettes[p].pal[4*i+1] = g; -			st->palettes[p].pal[4*i+2] = b; +			st->palettes[p].pal[4 * i] = r; +			st->palettes[p].pal[4 * i + 1] = g; +			st->palettes[p].pal[4 * i + 2] = b;  		}  		p++;  	} @@ -184,7 +464,7 @@ AnimationState *MoviePlayer::initAnimation(const char *name) {  	}  	// Load and configure decoder -	st->decoder = mpeg2_init (); +	st->decoder = mpeg2_init();  	if (st->decoder == NULL) {  		warning("Cutscene: Could not allocate an MPEG2 decoder");  		delete st; @@ -195,34 +475,48 @@ AnimationState *MoviePlayer::initAnimation(const char *name) {  	st->framenum = 0;  	// Load in palette data -	st->lutcalcnum =  (BITDEPTH + st->palettes[st->palnum].end + 2) / (st->palettes[st->palnum].end + 2); - +	st->lutcalcnum = (BITDEPTH + st->palettes[st->palnum].end + 2) / (st->palettes[st->palnum].end + 2);  	/* Play audio - TODO: Sync with video?*/ + +#ifdef USE_VORBIS  	// Another TODO: There is no reason that this only allows OGG, and not MP3, or any other format  	// the mixer might support one day... is there?  	File *sndFile = new File;  	sprintf(tempFile, "%s.ogg", basename);  	if (sndFile->open(tempFile))  		_vm->_mixer->playVorbis(&st->bgSound, sndFile, 100000000); +#endif  	// FIXME: This leaks (sndFile will never be deleted)  	return st;  } -#endif -#ifndef USE_MPEG2 -void MoviePlayer::doneAnimation(AnimationState *st) { -} -#else  void MoviePlayer::doneAnimation(AnimationState *st) { -	_vm->_mixer->stopHandle(st->bgSound); +	if (st->bgSound.isActive()) +		_vm->_mixer->stopHandle(st->bgSound);  	mpeg2_close (st->decoder);  	st->mpgfile->close();  	delete st->mpgfile;  	delete st;  } + +#else + +bool MoviePlayer::decodeFrame(AnimationState * st) { +	// Dummy for MPEG2-less builds +	return false; +} + +AnimationState *MoviePlayer::initAnimation(const char *name) { +	return 0; +} + +void MoviePlayer::doneAnimation(AnimationState *st) { +} +  #endif +  } // End of namespace Sword2  | 
