/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code 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. Doom 3 Source Code 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "gamesys/SysCvar.h" #include "script/Script_Thread.h" #include "Player.h" #include "Camera.h" /* =============================================================================== idCamera Base class for cameras =============================================================================== */ ABSTRACT_DECLARATION( idEntity, idCamera ) END_CLASS /* ===================== idCamera::Spawn ===================== */ void idCamera::Spawn( void ) { } /* ===================== idCamera::GetRenderView ===================== */ renderView_t *idCamera::GetRenderView() { renderView_t *rv = idEntity::GetRenderView(); GetViewParms( rv ); return rv; } /*********************************************************************** idCameraView ***********************************************************************/ const idEventDef EV_Camera_SetAttachments( "", NULL ); CLASS_DECLARATION( idCamera, idCameraView ) EVENT( EV_Activate, idCameraView::Event_Activate ) EVENT( EV_Camera_SetAttachments, idCameraView::Event_SetAttachments ) END_CLASS /* =============== idCameraView::idCameraView ================ */ idCameraView::idCameraView() { fov = 90.0f; attachedTo = NULL; attachedView = NULL; } /* =============== idCameraView::Save ================ */ void idCameraView::Save( idSaveGame *savefile ) const { savefile->WriteFloat( fov ); savefile->WriteObject( attachedTo ); savefile->WriteObject( attachedView ); } /* =============== idCameraView::Restore ================ */ void idCameraView::Restore( idRestoreGame *savefile ) { savefile->ReadFloat( fov ); savefile->ReadObject( reinterpret_cast( attachedTo ) ); savefile->ReadObject( reinterpret_cast( attachedView ) ); } /* =============== idCameraView::Event_SetAttachments ================ */ void idCameraView::Event_SetAttachments( ) { SetAttachment( &attachedTo, "attachedTo" ); SetAttachment( &attachedView, "attachedView" ); } /* =============== idCameraView::Event_Activate ================ */ void idCameraView::Event_Activate( idEntity *activator ) { if (spawnArgs.GetBool("trigger")) { if (gameLocal.GetCamera() != this) { if ( g_debugCinematic.GetBool() ) { gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); } gameLocal.SetCamera(this); } else { if ( g_debugCinematic.GetBool() ) { gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); } gameLocal.SetCamera(NULL); } } } /* ===================== idCameraView::Stop ===================== */ void idCameraView::Stop( void ) { if ( g_debugCinematic.GetBool() ) { gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); } gameLocal.SetCamera(NULL); ActivateTargets( gameLocal.GetLocalPlayer() ); } /* ===================== idCameraView::Spawn ===================== */ void idCameraView::SetAttachment( idEntity **e, const char *p ) { const char *cam = spawnArgs.GetString( p ); if ( strlen ( cam ) ) { *e = gameLocal.FindEntity( cam ); } } /* ===================== idCameraView::Spawn ===================== */ void idCameraView::Spawn( void ) { // if no target specified use ourself const char *cam = spawnArgs.GetString("cameraTarget"); if ( strlen ( cam ) == 0) { spawnArgs.Set("cameraTarget", spawnArgs.GetString("name")); } fov = spawnArgs.GetFloat("fov", "90"); PostEventMS( &EV_Camera_SetAttachments, 0 ); UpdateChangeableSpawnArgs(NULL); } /* ===================== idCameraView::GetViewParms ===================== */ void idCameraView::GetViewParms( renderView_t *view ) { assert( view ); if (view == NULL) { return; } idVec3 dir; idEntity *ent; if ( attachedTo ) { ent = attachedTo; } else { ent = this; } view->vieworg = ent->GetPhysics()->GetOrigin(); if ( attachedView ) { dir = attachedView->GetPhysics()->GetOrigin() - view->vieworg; dir.Normalize(); view->viewaxis = dir.ToMat3(); } else { view->viewaxis = ent->GetPhysics()->GetAxis(); } gameLocal.CalcFov( fov, view->fov_x, view->fov_y ); } /* =============================================================================== idCameraAnim =============================================================================== */ const idEventDef EV_Camera_Start( "start", NULL ); const idEventDef EV_Camera_Stop( "stop", NULL ); CLASS_DECLARATION( idCamera, idCameraAnim ) EVENT( EV_Thread_SetCallback, idCameraAnim::Event_SetCallback ) EVENT( EV_Camera_Stop, idCameraAnim::Event_Stop ) EVENT( EV_Camera_Start, idCameraAnim::Event_Start ) EVENT( EV_Activate, idCameraAnim::Event_Activate ) END_CLASS /* ===================== idCameraAnim::idCameraAnim ===================== */ idCameraAnim::idCameraAnim() { threadNum = 0; offset.Zero(); frameRate = 0; cycle = 1; starttime = 0; activator = NULL; } /* ===================== idCameraAnim::~idCameraAnim ===================== */ idCameraAnim::~idCameraAnim() { if ( gameLocal.GetCamera() == this ) { gameLocal.SetCamera( NULL ); } } /* =============== idCameraAnim::Save ================ */ void idCameraAnim::Save( idSaveGame *savefile ) const { savefile->WriteInt( threadNum ); savefile->WriteVec3( offset ); savefile->WriteInt( frameRate ); savefile->WriteInt( starttime ); savefile->WriteInt( cycle ); activator.Save( savefile ); } /* =============== idCameraAnim::Restore ================ */ void idCameraAnim::Restore( idRestoreGame *savefile ) { savefile->ReadInt( threadNum ); savefile->ReadVec3( offset ); savefile->ReadInt( frameRate ); savefile->ReadInt( starttime ); savefile->ReadInt( cycle ); activator.Restore( savefile ); LoadAnim(); } /* ===================== idCameraAnim::Spawn ===================== */ void idCameraAnim::Spawn( void ) { if ( spawnArgs.GetVector( "old_origin", "0 0 0", offset ) ) { offset = GetPhysics()->GetOrigin() - offset; } else { offset.Zero(); } // always think during cinematics cinematic = true; LoadAnim(); } /* ================ idCameraAnim::Load ================ */ void idCameraAnim::LoadAnim( void ) { int version; idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); idToken token; int numFrames; int numCuts; int i; idStr filename; const char *key; key = spawnArgs.GetString( "anim" ); if ( !key ) { gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() ); } filename = spawnArgs.GetString( va( "anim %s", key ) ); if ( !filename.Length() ) { gameLocal.Error( "Missing 'anim %s' key on '%s'", key, name.c_str() ); } filename.SetFileExtension( MD5_CAMERA_EXT ); if ( !parser.LoadFile( filename ) ) { gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() ); } cameraCuts.Clear(); cameraCuts.SetGranularity( 1 ); camera.Clear(); camera.SetGranularity( 1 ); parser.ExpectTokenString( MD5_VERSION_STRING ); version = parser.ParseInt(); if ( version != MD5_VERSION ) { parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); } // skip the commandline parser.ExpectTokenString( "commandline" ); parser.ReadToken( &token ); // parse num frames parser.ExpectTokenString( "numFrames" ); numFrames = parser.ParseInt(); if ( numFrames <= 0 ) { parser.Error( "Invalid number of frames: %d", numFrames ); } // parse framerate parser.ExpectTokenString( "frameRate" ); frameRate = parser.ParseInt(); if ( frameRate <= 0 ) { parser.Error( "Invalid framerate: %d", frameRate ); } // parse num cuts parser.ExpectTokenString( "numCuts" ); numCuts = parser.ParseInt(); if ( ( numCuts < 0 ) || ( numCuts > numFrames ) ) { parser.Error( "Invalid number of camera cuts: %d", numCuts ); } // parse the camera cuts parser.ExpectTokenString( "cuts" ); parser.ExpectTokenString( "{" ); cameraCuts.SetNum( numCuts ); for( i = 0; i < numCuts; i++ ) { cameraCuts[ i ] = parser.ParseInt(); if ( ( cameraCuts[ i ] < 1 ) || ( cameraCuts[ i ] >= numFrames ) ) { parser.Error( "Invalid camera cut" ); } } parser.ExpectTokenString( "}" ); // parse the camera frames parser.ExpectTokenString( "camera" ); parser.ExpectTokenString( "{" ); camera.SetNum( numFrames ); for( i = 0; i < numFrames; i++ ) { parser.Parse1DMatrix( 3, camera[ i ].t.ToFloatPtr() ); parser.Parse1DMatrix( 3, camera[ i ].q.ToFloatPtr() ); camera[ i ].fov = parser.ParseFloat(); } parser.ExpectTokenString( "}" ); #if 0 if ( !gameLocal.GetLocalPlayer() ) { return; } idDebugGraph gGraph; idDebugGraph tGraph; idDebugGraph qGraph; idDebugGraph dtGraph; idDebugGraph dqGraph; gGraph.SetNumSamples( numFrames ); tGraph.SetNumSamples( numFrames ); qGraph.SetNumSamples( numFrames ); dtGraph.SetNumSamples( numFrames ); dqGraph.SetNumSamples( numFrames ); gameLocal.Printf( "\n\ndelta vec:\n" ); float diff_t, last_t, t; float diff_q, last_q, q; diff_t = last_t = 0.0f; diff_q = last_q = 0.0f; for( i = 1; i < numFrames; i++ ) { t = ( camera[ i ].t - camera[ i - 1 ].t ).Length(); q = ( camera[ i ].q.ToQuat() - camera[ i - 1 ].q.ToQuat() ).Length(); diff_t = t - last_t; diff_q = q - last_q; gGraph.AddValue( ( i % 10 ) == 0 ); tGraph.AddValue( t ); qGraph.AddValue( q ); dtGraph.AddValue( diff_t ); dqGraph.AddValue( diff_q ); gameLocal.Printf( "%d: %.8f : %.8f, %.8f : %.8f\n", i, t, diff_t, q, diff_q ); last_t = t; last_q = q; } gGraph.Draw( colorBlue, 300.0f ); tGraph.Draw( colorOrange, 60.0f ); dtGraph.Draw( colorYellow, 6000.0f ); qGraph.Draw( colorGreen, 60.0f ); dqGraph.Draw( colorCyan, 6000.0f ); #endif } /* =============== idCameraAnim::Start ================ */ void idCameraAnim::Start( void ) { cycle = spawnArgs.GetInt( "cycle" ); if ( !cycle ) { cycle = 1; } if ( g_debugCinematic.GetBool() ) { gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); } starttime = gameLocal.time; gameLocal.SetCamera( this ); BecomeActive( TH_THINK ); // if the player has already created the renderview for this frame, have him update it again so that the camera starts this frame if ( gameLocal.GetLocalPlayer()->GetRenderView()->time == gameLocal.time ) { gameLocal.GetLocalPlayer()->CalculateRenderView(); } } /* ===================== idCameraAnim::Stop ===================== */ void idCameraAnim::Stop( void ) { if ( gameLocal.GetCamera() == this ) { if ( g_debugCinematic.GetBool() ) { gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); } BecomeInactive( TH_THINK ); gameLocal.SetCamera( NULL ); if ( threadNum ) { idThread::ObjectMoveDone( threadNum, this ); threadNum = 0; } ActivateTargets( activator.GetEntity() ); } } /* ===================== idCameraAnim::Think ===================== */ void idCameraAnim::Think( void ) { int frame; int frameTime; if ( thinkFlags & TH_THINK ) { // check if we're done in the Think function when the cinematic is being skipped (idCameraAnim::GetViewParms isn't called when skipping cinematics). if ( !gameLocal.skipCinematic ) { return; } if ( camera.Num() < 2 ) { // 1 frame anims never end return; } if ( frameRate == USERCMD_HZ ) { frameTime = gameLocal.time - starttime; frame = frameTime / gameLocal.msec; } else { frameTime = ( gameLocal.time - starttime ) * frameRate; frame = frameTime / 1000; } if ( frame > camera.Num() + cameraCuts.Num() - 2 ) { if ( cycle > 0 ) { cycle--; } if ( cycle != 0 ) { // advance start time so that we loop starttime += ( ( camera.Num() - cameraCuts.Num() ) * 1000 ) / frameRate; } else { Stop(); } } } } /* ===================== idCameraAnim::GetViewParms ===================== */ void idCameraAnim::GetViewParms( renderView_t *view ) { int realFrame; int frame; int frameTime; float lerp; float invlerp; cameraFrame_t *camFrame; int i; int cut; idQuat q1, q2, q3; assert( view ); if ( !view ) { return; } if ( camera.Num() == 0 ) { // we most likely are in the middle of a restore // FIXME: it would be better to fix it so this doesn't get called during a restore return; } if ( frameRate == USERCMD_HZ ) { frameTime = gameLocal.time - starttime; frame = frameTime / gameLocal.msec; lerp = 0.0f; } else { frameTime = ( gameLocal.time - starttime ) * frameRate; frame = frameTime / 1000; lerp = ( frameTime % 1000 ) * 0.001f; } // skip any frames where camera cuts occur realFrame = frame; cut = 0; for( i = 0; i < cameraCuts.Num(); i++ ) { if ( frame < cameraCuts[ i ] ) { break; } frame++; cut++; } if ( g_debugCinematic.GetBool() ) { int prevFrameTime = ( gameLocal.time - starttime - gameLocal.msec ) * frameRate; int prevFrame = prevFrameTime / 1000; int prevCut; prevCut = 0; for( i = 0; i < cameraCuts.Num(); i++ ) { if ( prevFrame < cameraCuts[ i ] ) { break; } prevFrame++; prevCut++; } if ( prevCut != cut ) { gameLocal.Printf( "%d: '%s' cut %d\n", gameLocal.framenum, GetName(), cut ); } } // clamp to the first frame. also check if this is a one frame anim. one frame anims would end immediately, // but since they're mainly used for static cams anyway, just stay on it infinitely. if ( ( frame < 0 ) || ( camera.Num() < 2 ) ) { view->viewaxis = camera[ 0 ].q.ToQuat().ToMat3(); view->vieworg = camera[ 0 ].t + offset; view->fov_x = camera[ 0 ].fov; } else if ( frame > camera.Num() - 2 ) { if ( cycle > 0 ) { cycle--; } if ( cycle != 0 ) { // advance start time so that we loop starttime += ( ( camera.Num() - cameraCuts.Num() ) * 1000 ) / frameRate; GetViewParms( view ); return; } Stop(); if ( gameLocal.GetCamera() != NULL ) { // we activated another camera when we stopped, so get it's viewparms instead gameLocal.GetCamera()->GetViewParms( view ); return; } else { // just use our last frame camFrame = &camera[ camera.Num() - 1 ]; view->viewaxis = camFrame->q.ToQuat().ToMat3(); view->vieworg = camFrame->t + offset; view->fov_x = camFrame->fov; } } else if ( lerp == 0.0f ) { camFrame = &camera[ frame ]; view->viewaxis = camFrame[ 0 ].q.ToMat3(); view->vieworg = camFrame[ 0 ].t + offset; view->fov_x = camFrame[ 0 ].fov; } else { camFrame = &camera[ frame ]; invlerp = 1.0f - lerp; q1 = camFrame[ 0 ].q.ToQuat(); q2 = camFrame[ 1 ].q.ToQuat(); q3.Slerp( q1, q2, lerp ); view->viewaxis = q3.ToMat3(); view->vieworg = camFrame[ 0 ].t * invlerp + camFrame[ 1 ].t * lerp + offset; view->fov_x = camFrame[ 0 ].fov * invlerp + camFrame[ 1 ].fov * lerp; } gameLocal.CalcFov( view->fov_x, view->fov_x, view->fov_y ); // setup the pvs for this frame UpdatePVSAreas( view->vieworg ); #if 0 static int lastFrame = 0; static idVec3 lastFrameVec( 0.0f, 0.0f, 0.0f ); if ( gameLocal.time != lastFrame ) { gameRenderWorld->DebugBounds( colorCyan, idBounds( view->vieworg ).Expand( 16.0f ), vec3_origin, gameLocal.msec ); gameRenderWorld->DebugLine( colorRed, view->vieworg, view->vieworg + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); gameRenderWorld->DebugLine( colorCyan, lastFrameVec, view->vieworg, 10000, false ); gameRenderWorld->DebugLine( colorYellow, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 66.0f, 10000, false ); gameRenderWorld->DebugLine( colorOrange, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 64.0f + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); lastFrameVec = view->vieworg; lastFrame = gameLocal.time; } #endif if ( g_showcamerainfo.GetBool() ) { gameLocal.Printf( "^5Frame: ^7%d/%d\n\n\n", realFrame + 1, camera.Num() - cameraCuts.Num() ); } } /* =============== idCameraAnim::Event_Activate ================ */ void idCameraAnim::Event_Activate( idEntity *_activator ) { activator = _activator; if ( thinkFlags & TH_THINK ) { Stop(); } else { Start(); } } /* =============== idCameraAnim::Event_Start ================ */ void idCameraAnim::Event_Start( void ) { Start(); } /* =============== idCameraAnim::Event_Stop ================ */ void idCameraAnim::Event_Stop( void ) { Stop(); } /* ================ idCameraAnim::Event_SetCallback ================ */ void idCameraAnim::Event_SetCallback( void ) { if ( ( gameLocal.GetCamera() == this ) && !threadNum ) { threadNum = idThread::CurrentThreadNum(); idThread::ReturnInt( true ); } else { idThread::ReturnInt( false ); } }