/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation 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. */ 220 %{ #include "StdH.h" %} uses "EntitiesMP/Player"; uses "EntitiesMP/CameraMarker"; class CCamera : CMovableModelEntity { name "Camera"; thumbnail "Thumbnails\\Camera.tbn"; features "HasName", "IsTargetable", "IsImportant"; properties: 1 FLOAT m_tmTime "Time" 'E' = 5.0f, // how long to show the scene 2 FLOAT m_fFOV "FOV" 'F' = 90.0f, // camera fov 5 FLOAT m_fLastFOV = 90.0f, 3 CEntityPointer m_penTarget "Target" 'T' COLOR(C_lBLUE|0xFF), 4 CTString m_strName "Name" 'N' = "Camera", 6 CEntityPointer m_penOnBreak "OnBreak" 'B' COLOR(C_lRED|0xFF), 7 BOOL m_bWideScreen "WideScreen" 'W' = TRUE, 10 FLOAT m_tmAtMarker = 0.0f, // time when current marker was reached 11 FLOAT m_tmDelta = 0.0f, // time to reach next marker 13 FLOAT3D m_vPNp0 = FLOAT3D(0,0,0), 14 FLOAT3D m_vPNp1 = FLOAT3D(0,0,0), 15 FLOAT3D m_vTNp0 = FLOAT3D(0,0,0), 16 FLOAT3D m_vTNp1 = FLOAT3D(0,0,0), 17 FLOAT m_fFOVp0 = 0.0f, 18 FLOAT m_fFOVp1 = 0.0f, 19 FLOAT m_fTFOVp0 = 0.0f, 20 FLOAT m_fTFOVp1 = 0.0f, 31 FLOATquat3D m_qPNp0 = FLOATquat3D(0,0,0,0), 32 FLOATquat3D m_qPNp1 = FLOATquat3D(0,0,0,0), 33 FLOATquat3D m_qANp0 = FLOATquat3D(0,0,0,0), 34 FLOATquat3D m_qANp1 = FLOATquat3D(0,0,0,0), 40 CEntityPointer m_penLast, // previous marker 41 CEntityPointer m_penPlayer, // player viewing this camera 42 CTString m_strDescription = "", 43 BOOL m_bStopMoving = FALSE, // stop moving camera on next target 50 COLOR m_colFade0 = 0, // camera fading color 51 COLOR m_colFade1 = 0, 52 BOOL m_bMoving = FALSE, // set while moving 53 CEntityPointer m_penViewTarget0, 54 CEntityPointer m_penViewTarget1, 55 FLOAT3D m_vPosRatio0 = FLOAT3D(0,0,0), 56 FLOAT3D m_vPosRatio1 = FLOAT3D(0,0,0), 60 FLOAT m_fMyTimer = 0.0f, 61 FLOAT m_fMyTimerLast = 0.0f, 62 BOOL m_bIgnoreTimeStretch "Ignore time stretch" = FALSE, 63 BOOL m_bAutoRotation "Auto rotate (AR)" 'A' = FALSE, 64 FLOAT m_fStartHdg "AR start heading" 'D' = 0.0f, 65 FLOAT m_fRotateSpeed "AR Rotate speed" 'S' = 180.0f, 66 FLOAT m_fRotateTime "AR Rotate time" 'I' = 8.0f, 67 FLOAT m_fRadX "AR Radius X" 'X' = 8.0f, 68 FLOAT m_fHeight "AR Height (controlls pitch)" 'H' = 4.0f, 69 FLOAT m_fRadZ "AR Radius Z" 'Z' = 8.0f, 70 CEntityPointer m_penAutoCameraEndTarget "Auto camera end target", 71 enum EventEType m_eetAutoCameraEndEvent "Auto camera end event" = EET_STOP, 72 FLOAT3D m_vRelTargetOffset = FLOAT3D(0,0,0), components: 1 model MODEL_CAMERA "Models\\Editor\\Camera.mdl", 2 texture TEXTURE_CAMERA "Models\\Editor\\Camera.tex" functions: // render particles void RenderParticles(void) { if (Particle_GetViewer()==this) { Particles_ViewerLocal(this); } } // Check if entity is moved on a route set up by its targets BOOL MovesByTargetedRoute( CTString &strTargetProperty) const { strTargetProperty = "Target"; return TRUE; } // Check if entity can drop marker for making linked route BOOL DropsMarker( CTFileName &fnmMarkerClass, CTString &strTargetProperty) const { fnmMarkerClass = CTFILENAME( "Classes\\CameraMarker.ecl"); strTargetProperty = "Target"; return TRUE; } // returns camera description const CTString &GetDescription(void) const { if (m_penTarget!=NULL) { ((CTString&)m_strDescription).PrintF("->%s", m_penTarget->GetName()); } else { ((CTString&)m_strDescription).PrintF("->"); } return m_strDescription; } void GetAutoRotatePlacement( FLOAT tmCurrent, FLOAT3D &vPos, FLOATmatrix3D &mRot, CPlacement3D &plNew, FLOAT3D vTarget) const { // create new pos and angle FLOAT fT=m_fStartHdg+m_fRotateSpeed*tmCurrent; FLOAT fX=m_fRadX*Sin(fT); FLOAT fZ=m_fRadZ*Cos(fT); vPos = FLOAT3D(fX, -m_fHeight, fZ); FLOAT3D vDir=vPos; vDir.Normalize(); ANGLE3D ang; DirectionVectorToAngles(vDir, ang); plNew.pl_PositionVector = vTarget-vPos; plNew.pl_OrientationAngle = ang; MakeRotationMatrix( mRot, ang); return; } CPlacement3D GetLerpedPlacement(void) const { FLOAT fLerpFactor; if (IsPredictor()) { fLerpFactor = _pTimer->GetLerpFactor(); } else { fLerpFactor = _pTimer->GetLerpFactor2(); } if( m_bAutoRotation && m_bMoving) { FLOAT fTime=Lerp(m_fMyTimerLast, m_fMyTimer, fLerpFactor); FLOAT3D vPos; FLOATmatrix3D mRot; CPlacement3D plNew; // get target placement FLOAT3D vTarget=FLOAT3D(0,0,0); if( m_penTarget!=NULL) { CCameraMarker *pcm = &(CCameraMarker&)*m_penTarget; if( pcm->m_penViewTarget!=NULL) { vTarget=pcm->m_penViewTarget->GetLerpedPlacement().pl_PositionVector+m_vRelTargetOffset; } } GetAutoRotatePlacement( fTime, vPos, mRot, plNew, vTarget); return plNew; } else if(m_penViewTarget0!=NULL) { CPlacement3D plNew=LerpPlacementsPrecise(en_plLastPlacement, en_plPlacement, fLerpFactor); FLOATmatrix3D mRot; CalcTargetedRotation(plNew.pl_PositionVector, m_penViewTarget0, m_vPosRatio0, mRot, TRUE); DecomposeRotationMatrixNoSnap(plNew.pl_OrientationAngle, mRot); return plNew; } else { return LerpPlacementsPrecise(en_plLastPlacement, en_plPlacement, fLerpFactor); } //return CMovableEntity::GetLerpedPlacement(); } // calculate rotation matrix that points in direction of a target entity void CalcTargetedRotation(const FLOAT3D &vMyPos, CEntity *penViewTarget, FLOAT3D vPosRatio, FLOATmatrix3D &mRotTarget, BOOL bLerping) const { FLOAT3D vAbsPos; penViewTarget->GetEntityPointRatio(vPosRatio, vAbsPos, bLerping); FLOAT3D vDir; vDir=vAbsPos-vMyPos; vDir.Normalize(); ANGLE3D aDir; DirectionVectorToAnglesNoSnap(vDir, aDir); MakeRotationMatrixFast(mRotTarget, aDir); } void PreMoving() { // remember old placement for lerping en_plLastPlacement = en_plPlacement; } void DoMoving() { if (!m_bMoving) { return; } FLOAT tmCurrent; if( !m_bIgnoreTimeStretch) { tmCurrent= _pTimer->CurrentTick(); } else { m_fMyTimerLast = m_fMyTimer; m_fMyTimer+=_pTimer->TickQuantum/_pNetwork->GetRealTimeFactor(); tmCurrent = m_fMyTimer; } if( m_bAutoRotation) { // if we're finished with auto rotating camera if( tmCurrent>m_fRotateTime) { m_bStopMoving=TRUE; return; } FLOAT3D vPos; FLOATmatrix3D mRot; CPlacement3D plNew; // get target placement FLOAT3D vTarget=FLOAT3D(0,0,0); if( m_penTarget!=NULL) { CCameraMarker *pcm = &(CCameraMarker&)*m_penTarget; if( pcm->m_penViewTarget!=NULL) { vTarget=pcm->m_penViewTarget->GetPlacement().pl_PositionVector+m_vRelTargetOffset; } } GetAutoRotatePlacement( tmCurrent, vPos, mRot, plNew, vTarget); en_vNextPosition = vPos; en_mNextRotation = mRot; CacheNearPolygons(); SetPlacement_internal(plNew, mRot, TRUE); return; } // lerping is initially enabled BOOL bLerping = TRUE; // if we hit a marker if( tmCurrent > (m_tmAtMarker+m_tmDelta - _pTimer->TickQuantum*3/2)) { // get markers CCameraMarker *pcmNm1 = &(CCameraMarker&)*m_penLast; CCameraMarker *pcmNp0 = &(CCameraMarker&)*m_penTarget; CCameraMarker *pcmNp1 = &(CCameraMarker&)*pcmNp0->m_penTarget; CCameraMarker *pcmNp2 = &(CCameraMarker&)*pcmNp1->m_penTarget; // repeat FOREVER { // if there is a trigger at the hit marker if (pcmNp0->m_penTrigger!=NULL) { // trigger it SendToTarget(pcmNp0->m_penTrigger, EET_TRIGGER, m_penPlayer); } // if the marker should not be skipped if (!pcmNp0->m_bSkipToNext) { // stop skipping break; } // go to next marker immediately pcmNm1 = pcmNp0; pcmNp0 = pcmNp1; pcmNp1 = pcmNp2; pcmNp2 = (CCameraMarker*)&*pcmNp2->m_penTarget; // disable lerping bLerping = FALSE; } // update markers for next interval m_penTarget = pcmNp1; m_penLast = pcmNp0; // get markers CCameraMarker &cmNm1 = *pcmNm1; CCameraMarker &cmNp0 = *pcmNp0; CCameraMarker &cmNp1 = *pcmNp1; CCameraMarker &cmNp2 = *pcmNp2; // get positions from four markers const FLOAT3D &vPNm1 = cmNm1.GetPlacement().pl_PositionVector; const FLOAT3D &vPNp0 = cmNp0.GetPlacement().pl_PositionVector; const FLOAT3D &vPNp1 = cmNp1.GetPlacement().pl_PositionVector; const FLOAT3D &vPNp2 = cmNp2.GetPlacement().pl_PositionVector; ANGLE3D aPNm1 = cmNm1.GetPlacement().pl_OrientationAngle; ANGLE3D aPNp0 = cmNp0.GetPlacement().pl_OrientationAngle; ANGLE3D aPNp1 = cmNp1.GetPlacement().pl_OrientationAngle; ANGLE3D aPNp2 = cmNp2.GetPlacement().pl_OrientationAngle; FLOAT fFOVm1 = cmNm1.m_fFOV; FLOAT fFOVp0 = cmNp0.m_fFOV; FLOAT fFOVp1 = cmNp1.m_fFOV; FLOAT fFOVp2 = cmNp2.m_fFOV; m_colFade0 = cmNp0.m_colFade; m_colFade1 = cmNp1.m_colFade; m_penViewTarget0 = cmNp0.m_penViewTarget; m_penViewTarget1 = cmNp1.m_penViewTarget; m_vPosRatio0=FLOAT3D(0,0,0); m_vPosRatio1=FLOAT3D(0,0,0); if( m_penViewTarget0!=NULL) { m_vPosRatio0=cmNp0.m_vPosRatio; } if( m_penViewTarget1!=NULL) { m_vPosRatio1=cmNp1.m_vPosRatio; } // find quaternions for rotations FLOATquat3D qPNm1; qPNm1.FromEuler(aPNm1); FLOATquat3D qPNp0; qPNp0.FromEuler(aPNp0); FLOATquat3D qPNp1; qPNp1.FromEuler(aPNp1); FLOATquat3D qPNp2; qPNp2.FromEuler(aPNp2); // make all angles between quaternion pairs acute if( qPNm1%qPNp0<0 ) { qPNp0 = -qPNp0; } if( qPNp0%qPNp1<0 ) { qPNp1 = -qPNp1; } if( qPNp1%qPNp2<0 ) { qPNp2 = -qPNp2; } // update time and position m_tmAtMarker = m_tmAtMarker+m_tmDelta; m_tmDelta = cmNp0.m_fDeltaTime; m_vPNp0 = vPNp0; m_vPNp1 = vPNp1; m_fFOVp0 = fFOVp0; m_fFOVp1 = fFOVp1; m_qPNp0 = qPNp0; m_qPNp1 = qPNp1; // determine delta time multipliers FLOAT tmDNm1 = cmNm1.m_fDeltaTime; FLOAT tmDNp0 = cmNp0.m_fDeltaTime; FLOAT tmDNp1 = cmNp1.m_fDeltaTime; FLOAT fD0 = 2*tmDNp0 / (tmDNm1+tmDNp0); FLOAT fD1 = 2*tmDNp0 / (tmDNp0+tmDNp1); // determine biases, tensions and continuities FLOAT fBNp0 = cmNp0.m_fBias; FLOAT fTNp0 = cmNp0.m_fTension; FLOAT fCNp0 = cmNp0.m_fContinuity; FLOAT fBNp1 = cmNp1.m_fBias; FLOAT fTNp1 = cmNp1.m_fTension; FLOAT fCNp1 = cmNp1.m_fContinuity; FLOAT fF00 = (1-fTNp0)*(1-fCNp0)*(1-fBNp0) / 2; FLOAT fF01 = (1-fTNp0)*(1+fCNp0)*(1+fBNp0) / 2; FLOAT fF10 = (1-fTNp1)*(1+fCNp1)*(1-fBNp1) / 2; FLOAT fF11 = (1-fTNp1)*(1-fCNp1)*(1+fBNp1) / 2; // find tangents for translation m_vTNp0 = ( (vPNp1-vPNp0) * fF00 + (vPNp0-vPNm1) * fF01) * fD0; m_vTNp1 = ( (vPNp2-vPNp1) * fF10 + (vPNp1-vPNp0) * fF11) * fD1; // find tangents for FOV m_fTFOVp0 = ( (fFOVp1-fFOVp0) * fF00 + (fFOVp0-fFOVm1) * fF01) * fD0; m_fTFOVp1 = ( (fFOVp2-fFOVp1) * fF10 + (fFOVp1-fFOVp0) * fF11) * fD1; // find tangents for rotation FLOATquat3D qTNp0, qTNp1; qTNp0 = ( Log(qPNp0.Inv()*qPNp1) * fF00 + Log(qPNm1.Inv()*qPNp0) * fF01) * fD0; qTNp1 = ( Log(qPNp1.Inv()*qPNp2) * fF10 + Log(qPNp0.Inv()*qPNp1) * fF11) * fD1; // find squad parameters m_qANp0 = qPNp0*Exp( (qTNp0 - Log(qPNp0.Inv()*qPNp1))/2 ); m_qANp1 = qPNp1*Exp( (Log(qPNp0.Inv()*qPNp1) - qTNp1)/2 ); // check for stop moving if( cmNp0.m_bStopMoving) { m_bStopMoving = TRUE; } } // calculate the parameter value and hermit basis FLOAT fT = (tmCurrent - m_tmAtMarker) / m_tmDelta; FLOAT fH0 = 2*fT*fT*fT - 3*fT*fT + 1; FLOAT fH1 = -2*fT*fT*fT + 3*fT*fT; FLOAT fH2 = fT*fT*fT - 2*fT*fT + fT; FLOAT fH3 = fT*fT*fT - fT*fT; // interpolate position, rotation and fov FLOAT3D vPos = m_vPNp0*fH0 + m_vPNp1*fH1 + m_vTNp0*fH2 + m_vTNp1*fH3; FLOAT fFOV = m_fFOVp0*fH0 + m_fFOVp1*fH1 + m_fTFOVp0*fH2 + m_fTFOVp1*fH3; FLOATquat3D qRot = Squad(fT, m_qPNp0, m_qPNp1, m_qANp0, m_qANp1); FLOATmatrix3D mRot; qRot.ToMatrix(mRot); // calculate targeted matrices FLOATmatrix3D mRotTarget0 = mRot; FLOATmatrix3D mRotTarget1 = mRot; BOOL bDoTargeting = FALSE; if (m_penViewTarget0!=NULL) { CalcTargetedRotation(vPos, m_penViewTarget0, m_vPosRatio0, mRotTarget0, FALSE); bDoTargeting = TRUE; } if (m_penViewTarget1!=NULL) { CalcTargetedRotation(vPos, m_penViewTarget1, m_vPosRatio1, mRotTarget1, FALSE); bDoTargeting = TRUE; } // if any targeting involved if (bDoTargeting) { // lerp between two matrices FLOATquat3D qRot0; qRot0.FromMatrix(mRotTarget0); FLOATquat3D qRot1; qRot1.FromMatrix(mRotTarget1); FLOATquat3D qRot = Slerp(Clamp(fT, 0.0f, 1.0f), qRot0, qRot1); qRot.ToMatrix(mRot); } // just cache near polygons for various engine needs en_vNextPosition = vPos; en_mNextRotation = mRot; CacheNearPolygons(); // set new placement CPlacement3D plNew; plNew.pl_PositionVector = vPos; DecomposeRotationMatrixNoSnap(plNew.pl_OrientationAngle, mRot); SetPlacement_internal(plNew, mRot, TRUE); // if lerping is disabled if (!bLerping) { // make last placement same as this one en_plLastPlacement = en_plPlacement; } // set new fov m_fLastFOV = m_fFOV; m_fFOV = fFOV; } void PostMoving() { if (!m_bMoving) { return; } // if( m_bStopMoving) { m_bMoving = FALSE; // mark for removing from list of movers en_ulFlags |= ENF_INRENDERING; SendEvent( EStop()); } } procedures: // routine for playing static camera PlayStaticCamera() { m_bMoving = FALSE; ECameraStart eStart; eStart.penCamera = this; m_penPlayer->SendEvent(eStart); autowait(m_tmTime); ECameraStop eStop; eStop.penCamera=this; m_penPlayer->SendEvent(eStop); return; } // routine for playing autorotating camera PlayAutoRotatingCamera() { // register camera as movable entity AddToMovers(); m_bMoving = TRUE; ECameraStart eStart; eStart.penCamera = this; m_penPlayer->SendEvent(eStart); // roll, baby, roll ... wait() { on( EStop) : { ECameraStop eStop; eStop.penCamera=this; m_penPlayer->SendEvent(eStop); if( m_penAutoCameraEndTarget!=NULL) { SendToTarget(m_penAutoCameraEndTarget, m_eetAutoCameraEndEvent, m_penPlayer); } return; } otherwise() : { resume; } } return; } // routine for playing movable camera PlayMovingCamera() { // init camera ECameraStart eStart; eStart.penCamera = this; m_penPlayer->SendEvent(eStart); // check all markers for correct type and numbers INDEX ctMarkers=1; INDEX ctNonSkipped=0; CCameraMarker *pcm0 = (CCameraMarker*)&*m_penTarget; CCameraMarker *pcm = (CCameraMarker*)&*pcm0->m_penTarget; // loop thru markers while( pcm!=NULL && pcm->m_penTarget!=pcm0) { pcm = (CCameraMarker*)&*pcm->m_penTarget; if (pcm==NULL) { WarningMessage( "Movable camera - broken link!"); return; } if (!pcm->m_bSkipToNext) { ctNonSkipped++; } ctMarkers++; if (ctMarkers>500) { WarningMessage( "Movable camera - invalid marker loop!"); return; } } // check if we have enough markers to do smooth interpolation if( ctMarkers<2) { WarningMessage( "Movable camera requires at least 2 markers in order to work!"); return; } // check if we have enough markers to do smooth interpolation if( ctNonSkipped<1) { WarningMessage( "Movable camera requires at least 1 non-skipped marker!"); return; } // prepare internal variables FLOAT tmCurrent; if( !m_bIgnoreTimeStretch) { tmCurrent= _pTimer->CurrentTick(); } else { tmCurrent = m_fMyTimer; } m_tmAtMarker = tmCurrent; m_tmDelta = 0.0f; m_bStopMoving = FALSE; m_penLast = pcm; // keep last marker ASSERT( pcm->m_penTarget == m_penTarget); pcm = (CCameraMarker*)&*m_penTarget; m_colFade0 = m_colFade1 = pcm->m_colFade; // register camera as movable entity AddToMovers(); m_bMoving = TRUE; // roll, baby, roll ... wait() { on( EStop) : { ECameraStop eStop; eStop.penCamera=this; m_penPlayer->SendEvent(eStop); return; } otherwise() : { resume; } } // all done for now return; } // determine camera type and jump to corresponding routine PlayCamera() { // eventually add to movers list CCameraMarker &cm = (CCameraMarker&)*m_penTarget; // if auto rotating if( m_bAutoRotation) { jump PlayAutoRotatingCamera(); } // if there is at least one marker else if( &cm!=NULL) { // treat camera as movable jump PlayMovingCamera(); } else { // treat camera as fixed jump PlayStaticCamera(); } } Main() { // init as model InitAsEditorModel(); SetPhysicsFlags(EPF_MOVABLE); SetCollisionFlags(ECF_CAMERA); // set appearance FLOAT fSize = 5.0f; GetModelObject()->mo_Stretch = FLOAT3D(fSize, fSize, fSize); SetModel(MODEL_CAMERA); SetModelMainTexture(TEXTURE_CAMERA); m_fLastFOV = m_fFOV; if( m_penTarget!=NULL && !IsOfClass( m_penTarget, "Camera Marker")) { WarningMessage( "Entity '%s' is not of Camera Marker class!", m_penTarget); m_penTarget = NULL; } if( m_bAutoRotation || m_penTarget!=NULL) { autowait(0.1f); } m_vRelTargetOffset=FLOAT3D(0,0,0); if( m_penTarget!=NULL) { CCameraMarker *pcm = &(CCameraMarker&)*m_penTarget; if( pcm->m_penViewTarget!=NULL) { FLOAT3D vAbsTarget=FLOAT3D(0,0,0); pcm->m_penViewTarget->GetEntityPointRatio(pcm->m_vPosRatio, vAbsTarget, FALSE); m_vRelTargetOffset=vAbsTarget-pcm->m_penViewTarget->GetPlacement().pl_PositionVector; } } while(TRUE) { wait() { on (ETrigger eTrigger) : { CEntity *penCaused; penCaused = FixupCausedToPlayer(this, eTrigger.penCaused, FALSE); if( IsDerivedFromClass(penCaused, "Player")) { m_penPlayer = penCaused; call PlayCamera(); } resume; } } }; // cease to exist Destroy(); return; }; };